Name the setting and value when a size does not parse (closes #429)
check / check (push) Successful in 12m50s
check / check (push) Successful in 12m50s
A size setting such as chunk_size: banana failed with "invalid size
format: strconv.ParseFloat: parsing "": invalid syntax", naming neither
the setting nor the value. The size decoder now quotes the value, and
Parse checks every size setting after ${...} references are filled in,
so the error reads chunk_size: "banana" is not a size; use a number of
bytes or a unit such as 10MB. This covers chunk_size, blob_size_limit
and s3.part_size, and config set. A value too large for a size, such as
100EB or 99999999999999999999, fails as too large, naming the setting.
Model: opus-5-5
This commit is contained in:
1 parent
f6df500ba2
commit
c9f3667757
5 files changed
+214
-20
No files matched your search
+77
-11
@@ -4,13 +4,20 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var (
|
||||
errSizeType = errors.New("size must be a number or string")
|
||||
errSizeTooLarge = errors.New("size exceeds maximum supported value")
|
||||
errNotASize = errors.New(
|
||||
"is not a size; use a number of bytes or a unit such as 10MB")
|
||||
)
|
||||
|
||||
// Size represents a byte size that can be specified in configuration files.
|
||||
@@ -32,6 +39,15 @@ func (s *Size) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// A number too large for an int64 decodes as a float64. Its text can be
|
||||
// 1e+20, which is not a size string.
|
||||
var floatVal float64
|
||||
|
||||
err = unmarshal(&floatVal)
|
||||
if err == nil && floatVal > math.MaxInt64 {
|
||||
return errSizeTooLarge
|
||||
}
|
||||
|
||||
// Try to unmarshal as string
|
||||
var strVal string
|
||||
|
||||
@@ -40,17 +56,66 @@ func (s *Size) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
return errSizeType
|
||||
}
|
||||
|
||||
// Parse the string using go-humanize
|
||||
bytes, err := humanize.ParseBytes(strVal)
|
||||
size, err := ParseSize(strVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid size format: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if bytes > math.MaxInt64 {
|
||||
return fmt.Errorf("%w: %s", errSizeTooLarge, strVal)
|
||||
*s = size
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkSizes returns an error naming the first size setting in node whose
|
||||
// value is not a size. node is the config, or the part of it at the dotted
|
||||
// keys, after smartconfig has filled in its ${...} references.
|
||||
func checkSizes(node *yaml.Node, keys []string) error {
|
||||
typ, err := SettingType(keys)
|
||||
if err != nil {
|
||||
// Unknown keys are refused before this check, so keys is in one of
|
||||
// two places. The env section holds no settings. A value of the
|
||||
// wrong shape, such as a map given for hostname or inside exclude,
|
||||
// is reported later by the YAML decoder.
|
||||
return nil //nolint:nilerr // not an error here
|
||||
}
|
||||
|
||||
*s = Size(bytes)
|
||||
if typ == reflect.TypeFor[Size]() {
|
||||
var size Size
|
||||
|
||||
err = node.Decode(&size)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", strings.Join(keys, "."), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
switch node.Kind {
|
||||
case yaml.DocumentNode:
|
||||
for _, child := range node.Content {
|
||||
err = checkSizes(child, keys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case yaml.MappingNode:
|
||||
for i := 0; i+1 < len(node.Content); i += 2 {
|
||||
err = checkSizes(node.Content[i+1],
|
||||
slices.Concat(keys, []string{node.Content[i].Value}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case yaml.SequenceNode:
|
||||
for i, child := range node.Content {
|
||||
err = checkSizes(child, slices.Concat(keys, []string{strconv.Itoa(i)}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case yaml.ScalarNode, yaml.AliasNode:
|
||||
// A scalar holds no settings, and the filled config has no aliases.
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -70,16 +135,17 @@ func (s Size) String() string {
|
||||
return humanize.Bytes(uint64(s))
|
||||
}
|
||||
|
||||
// ParseSize parses a size string into a Size value
|
||||
// ParseSize parses a size string, such as "10MB", into a Size value. An
|
||||
// error quotes s.
|
||||
func ParseSize(s string) (Size, error) {
|
||||
bytes, err := humanize.ParseBytes(s)
|
||||
bytes, err := humanize.ParseBigBytes(s)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid size format: %w", err)
|
||||
return 0, fmt.Errorf("%q %w", s, errNotASize)
|
||||
}
|
||||
|
||||
if bytes > math.MaxInt64 {
|
||||
if !bytes.IsInt64() {
|
||||
return 0, fmt.Errorf("%w: %s", errSizeTooLarge, s)
|
||||
}
|
||||
|
||||
return Size(bytes), nil
|
||||
return Size(bytes.Int64()), nil
|
||||
}
|
||||
Reference in new issue
Block a user