Skip to content

Commit 05b9b02

Browse files
committed
internal/envflag: support deprecated flags
We wish to remove support for disabling the modules experiment but we do not wish to break users who have set `CUE_EXPERIMENT=modules=1` to use the experiment. So change `envflag` to support marking a field as deprecated, meaning that any attempt to change it from its default value will result in an error. Also in passing remove some unnecessary type parameters. Signed-off-by: Roger Peppe <[email protected]> Change-Id: Ifd552d9212cda1516343d338a6dcc30f1febe150 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1200577 Reviewed-by: Daniel Martí <[email protected]> TryBot-Result: CUEcueckoo <[email protected]>
1 parent 2da59f2 commit 05b9b02

File tree

2 files changed

+66
-13
lines changed

2 files changed

+66
-13
lines changed

internal/envflag/flag.go

+35-11
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ func Init[T any](flags *T, envVar string) error {
2424
// The struct field tag may contain a default value other than the zero value,
2525
// such as `envflag:"default:true"` to set a boolean field to true by default.
2626
//
27+
// The tag may be marked as deprecated with `envflag:"deprecated"`
28+
// which will cause Parse to return an error if the user attempts to set
29+
// its value to anything but the default value.
30+
//
2731
// The string may contain a comma-separated list of name=value pairs values
2832
// representing the boolean fields in the struct type T. If the value is omitted
2933
// entirely, the value is assumed to be name=true.
@@ -34,25 +38,35 @@ func Init[T any](flags *T, envVar string) error {
3438
func Parse[T any](flags *T, env string) error {
3539
// Collect the field indices and set the default values.
3640
indexByName := make(map[string]int)
41+
deprecated := make(map[string]bool)
3742
fv := reflect.ValueOf(flags).Elem()
3843
ft := fv.Type()
3944
for i := 0; i < ft.NumField(); i++ {
4045
field := ft.Field(i)
46+
name := strings.ToLower(field.Name)
4147
defaultValue := false
4248
if tagStr, ok := field.Tag.Lookup("envflag"); ok {
43-
defaultStr, ok := strings.CutPrefix(tagStr, "default:")
44-
// TODO: consider panicking for these error types.
45-
if !ok {
46-
return fmt.Errorf("expected tag like `envflag:\"default:true\"`: %s", tagStr)
47-
}
48-
v, err := strconv.ParseBool(defaultStr)
49-
if err != nil {
50-
return fmt.Errorf("invalid default bool value for %s: %v", field.Name, err)
49+
for _, f := range strings.Split(tagStr, ",") {
50+
key, rest, hasRest := strings.Cut(f, ":")
51+
switch key {
52+
case "default":
53+
v, err := strconv.ParseBool(rest)
54+
if err != nil {
55+
return fmt.Errorf("invalid default bool value for %s: %v", field.Name, err)
56+
}
57+
defaultValue = v
58+
fv.Field(i).SetBool(defaultValue)
59+
case "deprecated":
60+
if hasRest {
61+
return fmt.Errorf("cannot have a value for deprecated tag")
62+
}
63+
deprecated[name] = true
64+
default:
65+
return fmt.Errorf("unknown envflag tag %q", f)
66+
}
5167
}
52-
defaultValue = v
53-
fv.Field(i).SetBool(defaultValue)
5468
}
55-
indexByName[strings.ToLower(field.Name)] = i
69+
indexByName[name] = i
5670
}
5771

5872
if env == "" {
@@ -80,6 +94,16 @@ func Parse[T any](flags *T, env string) error {
8094
errs = append(errs, fmt.Errorf("unknown %s", elem))
8195
continue
8296
}
97+
if deprecated[name] {
98+
// We allow setting deprecated flags to their default value so
99+
// that bold explorers will not be penalised for their
100+
// experimentation.
101+
if fv.Field(index).Bool() != value {
102+
errs = append(errs, fmt.Errorf("cannot change default value of deprecated flag %q", name))
103+
}
104+
continue
105+
}
106+
83107
fv.Field(index).SetBool(value)
84108
}
85109
return errors.Join(errs...)

internal/envflag/flag_test.go

+31-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ type testFlags struct {
1414
DefaultTrue bool `envflag:"default:true"`
1515
}
1616

17+
type deprecatedFlags struct {
18+
Foo bool `envflag:"deprecated"`
19+
Bar bool `envflag:"deprecated,default:true"`
20+
}
21+
1722
func success[T comparable](want T) func(t *testing.T) {
1823
return func(t *testing.T) {
1924
var x T
@@ -54,7 +59,7 @@ var tests = []struct {
5459
}, {
5560
testName: "Unknown",
5661
envVal: "ratchet",
57-
test: failure[testFlags](testFlags{DefaultTrue: true},
62+
test: failure(testFlags{DefaultTrue: true},
5863
"cannot parse TEST_VAR: unknown ratchet"),
5964
}, {
6065
testName: "Set",
@@ -73,7 +78,7 @@ var tests = []struct {
7378
}, {
7479
testName: "SetWithUnknown",
7580
envVal: "foo,other",
76-
test: failure[testFlags](testFlags{
81+
test: failure(testFlags{
7782
Foo: true,
7883
DefaultTrue: true,
7984
}, "cannot parse TEST_VAR: unknown other"),
@@ -108,6 +113,30 @@ var tests = []struct {
108113
testName: "Invalid",
109114
envVal: "foo=2,BarBaz=true",
110115
test: invalid(testFlags{DefaultTrue: true}),
116+
}, {
117+
testName: "DeprecatedWithFalseDefault",
118+
envVal: "foo=1",
119+
test: failure(deprecatedFlags{
120+
Bar: true,
121+
}, `cannot parse TEST_VAR: cannot change default value of deprecated flag "foo"`),
122+
}, {
123+
testName: "DeprecatedNoopWhenSameAndFalseDefault",
124+
envVal: "foo=false",
125+
test: success(deprecatedFlags{
126+
Bar: true,
127+
}),
128+
}, {
129+
testName: "DeprecatedWithTrueDefault",
130+
envVal: "bar=0",
131+
test: failure(deprecatedFlags{
132+
Bar: true,
133+
}, `cannot parse TEST_VAR: cannot change default value of deprecated flag "bar"`),
134+
}, {
135+
testName: "DeprecatedNoopWhenSameAndTrueDefault",
136+
envVal: "bar=1",
137+
test: success(deprecatedFlags{
138+
Bar: true,
139+
}),
111140
}}
112141

113142
func TestInit(t *testing.T) {

0 commit comments

Comments
 (0)