-
Notifications
You must be signed in to change notification settings - Fork 94
feat: support boolean and integer expansion #934
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8f7fd80
405ba4c
063e5fd
1afe61c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| .idea | ||
| *~ | ||
| bin/ | ||
| dist/ | ||
| cover.out | ||
| vals | ||
| .vscode |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,17 +3,22 @@ package expansion | |||||||||||||||
| import ( | ||||||||||||||||
| "fmt" | ||||||||||||||||
| "regexp" | ||||||||||||||||
| "slices" | ||||||||||||||||
| "strings" | ||||||||||||||||
| ) | ||||||||||||||||
|
|
||||||||||||||||
| type ExpandRegexMatch struct { | ||||||||||||||||
| Target *regexp.Regexp | ||||||||||||||||
| Lookup func(string) (string, error) | ||||||||||||||||
| Lookup func(string) (interface{}, error) | ||||||||||||||||
| Only []string | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| var DefaultRefRegexp = regexp.MustCompile(`((secret)?ref)\+([^\+:]*:\/\/[^\+\n ]+[^\+\n ",])\+?`) | ||||||||||||||||
|
|
||||||||||||||||
| func (e *ExpandRegexMatch) shouldExpand(kind string) bool { | ||||||||||||||||
| return len(e.Only) == 0 || slices.Contains(e.Only, kind) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| func (e *ExpandRegexMatch) InString(s string) (string, error) { | ||||||||||||||||
| var sb strings.Builder | ||||||||||||||||
| for { | ||||||||||||||||
|
|
@@ -22,40 +27,60 @@ func (e *ExpandRegexMatch) InString(s string) (string, error) { | |||||||||||||||
| sb.WriteString(s) | ||||||||||||||||
| return sb.String(), nil | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| kind := s[ixs[2]:ixs[3]] | ||||||||||||||||
| if len(e.Only) > 0 { | ||||||||||||||||
| var shouldExpand bool | ||||||||||||||||
| for _, k := range e.Only { | ||||||||||||||||
| if k == kind { | ||||||||||||||||
| shouldExpand = true | ||||||||||||||||
| break | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| if !shouldExpand { | ||||||||||||||||
| sb.WriteString(s) | ||||||||||||||||
| return sb.String(), nil | ||||||||||||||||
| } | ||||||||||||||||
| if !e.shouldExpand(kind) { | ||||||||||||||||
| sb.WriteString(s) | ||||||||||||||||
| // FIXME: this skips the rest of the string, is this intended? | ||||||||||||||||
| return sb.String(), nil | ||||||||||||||||
|
Comment on lines
+33
to
+35
|
||||||||||||||||
| sb.WriteString(s) | |
| // FIXME: this skips the rest of the string, is this intended? | |
| return sb.String(), nil | |
| // Keep this reference as-is and continue processing the rest of the string. | |
| sb.WriteString(s[:ixs[1]]) | |
| s = s[ixs[1]:] | |
| continue |
Copilot
AI
Jan 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new InValue function that enables type preservation for boolean and integer values lacks test coverage. Consider adding tests that verify type preservation for full matches and string conversion for partial matches, including edge cases like booleans, integers, and mixed scenarios.
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -119,9 +119,7 @@ const ( | |||||||||||||
| ProviderServercore = "servercore" | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| var ( | ||||||||||||||
| EnvFallbackPrefix = "VALS_" | ||||||||||||||
| ) | ||||||||||||||
| var EnvFallbackPrefix = "VALS_" | ||||||||||||||
|
|
||||||||||||||
| type Evaluator interface { | ||||||||||||||
| Eval(map[string]interface{}) (map[string]interface{}, error) | ||||||||||||||
|
|
@@ -344,25 +342,23 @@ func (r *Runtime) prepare() (*expansion.ExpandRegexMatch, error) { | |||||||||||||
| expand := expansion.ExpandRegexMatch{ | ||||||||||||||
| Only: only, | ||||||||||||||
| Target: expansion.DefaultRefRegexp, | ||||||||||||||
| Lookup: func(key string) (string, error) { | ||||||||||||||
| Lookup: func(key string) (interface{}, error) { | ||||||||||||||
| if val, ok := r.docCache.Get(key); ok { | ||||||||||||||
| valStr, ok := val.(string) | ||||||||||||||
| if ok { | ||||||||||||||
| return valStr, nil | ||||||||||||||
| if isTerminalValue(val) { | ||||||||||||||
| return val, nil | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| uri, err := url.Parse(key) | ||||||||||||||
| if err != nil { | ||||||||||||||
| return "", err | ||||||||||||||
| return nil, err | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| hash := uriToProviderHash(uri) | ||||||||||||||
|
|
||||||||||||||
| p, err := updateProviders(uri, hash) | ||||||||||||||
|
|
||||||||||||||
| if err != nil { | ||||||||||||||
| return "", err | ||||||||||||||
| return nil, err | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| var frag string | ||||||||||||||
|
|
@@ -401,12 +397,12 @@ func (r *Runtime) prepare() (*expansion.ExpandRegexMatch, error) { | |||||||||||||
| if cachedStr, ok := r.strCache.Get(cacheKey); ok { | ||||||||||||||
| str, ok = cachedStr.(string) | ||||||||||||||
| if !ok { | ||||||||||||||
| return "", fmt.Errorf("error reading str from cache: unsupported value type %T", cachedStr) | ||||||||||||||
| return nil, fmt.Errorf("error reading str from cache: unsupported value type %T", cachedStr) | ||||||||||||||
| } | ||||||||||||||
| } else { | ||||||||||||||
| str, err = p.GetString(path) | ||||||||||||||
| if err != nil { | ||||||||||||||
| return "", err | ||||||||||||||
| return nil, err | ||||||||||||||
| } | ||||||||||||||
| r.strCache.Add(cacheKey, str) | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -418,7 +414,7 @@ func (r *Runtime) prepare() (*expansion.ExpandRegexMatch, error) { | |||||||||||||
| if cachedMap, ok := r.docCache.Get(mapRequestURI); ok { | ||||||||||||||
| obj, ok = cachedMap.(map[string]interface{}) | ||||||||||||||
| if !ok { | ||||||||||||||
| return "", fmt.Errorf("error reading map from cache: unsupported value type %T", cachedMap) | ||||||||||||||
| return nil, fmt.Errorf("error reading map from cache: unsupported value type %T", cachedMap) | ||||||||||||||
| } | ||||||||||||||
| } else if uri.Scheme == "httpjson" { | ||||||||||||||
| // Due to the unpredictability in the structure of the JSON object, | ||||||||||||||
|
|
@@ -430,49 +426,61 @@ func (r *Runtime) prepare() (*expansion.ExpandRegexMatch, error) { | |||||||||||||
| // object, accommodating different configurations and variations. | ||||||||||||||
| value, err := p.GetString(key) | ||||||||||||||
| if err != nil { | ||||||||||||||
| return "", err | ||||||||||||||
| return nil, err | ||||||||||||||
| } | ||||||||||||||
| return value, nil | ||||||||||||||
| } else { | ||||||||||||||
| obj, err = p.GetStringMap(path) | ||||||||||||||
| if err != nil { | ||||||||||||||
| return "", err | ||||||||||||||
| return nil, err | ||||||||||||||
| } | ||||||||||||||
| r.docCache.Add(mapRequestURI, obj) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| keys := strings.Split(frag, "/") | ||||||||||||||
| for i, k := range keys { | ||||||||||||||
| newobj := map[string]interface{}{} | ||||||||||||||
| switch t := obj[k].(type) { | ||||||||||||||
| case string: | ||||||||||||||
| t := obj[k] | ||||||||||||||
| if isTerminalValue(t) { | ||||||||||||||
| if i != len(keys)-1 { | ||||||||||||||
| return "", fmt.Errorf("unexpected type of value for key at %d=%s in %v: expected map[string]interface{}, got %v(%T)", i, k, keys, t, t) | ||||||||||||||
| return nil, fmt.Errorf("unexpected type of value for key at %d=%s in %v: expected map[string]interface{}, got %v(%T)", i, k, keys, t, t) | ||||||||||||||
| } | ||||||||||||||
| r.docCache.Add(key, t) | ||||||||||||||
| return t, nil | ||||||||||||||
| } | ||||||||||||||
| switch t := t.(type) { | ||||||||||||||
| case map[string]interface{}: | ||||||||||||||
| newobj = t | ||||||||||||||
| obj = t | ||||||||||||||
| case map[interface{}]interface{}: | ||||||||||||||
| obj := map[string]interface{}{} | ||||||||||||||
| for k, v := range t { | ||||||||||||||
| newobj[fmt.Sprintf("%v", k)] = v | ||||||||||||||
| obj[fmt.Sprintf("%v", k)] = v | ||||||||||||||
| } | ||||||||||||||
| default: | ||||||||||||||
| return nil, fmt.Errorf("unsupported type for key at %d=%s in %v: %v(%T)", i, k, keys, t, t) | ||||||||||||||
| } | ||||||||||||||
| obj = newobj | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if r.Options.FailOnMissingKeyInMap { | ||||||||||||||
| return "", fmt.Errorf("no value found for key %s", frag) | ||||||||||||||
| return nil, fmt.Errorf("no value found for key %s", frag) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return "", nil | ||||||||||||||
| return nil, nil | ||||||||||||||
| } | ||||||||||||||
| }, | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return &expand, nil | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func isTerminalValue(v any) bool { | ||||||||||||||
| switch v.(type) { | ||||||||||||||
| case bool, int, string: | ||||||||||||||
|
||||||||||||||
| case bool, int, string: | |
| case bool, | |
| int, int8, int16, int32, int64, | |
| uint, uint8, uint16, uint32, uint64, | |
| float32, float64, | |
| string: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like the code should just skip to the end of the current match.
Something like this: