Skip to content

Commit ecdc78c

Browse files
authored
fix: Propagating flag parse errors instead of swallowing them (#6732)
1 parent c110ef2 commit ecdc78c

2 files changed

Lines changed: 91 additions & 4 deletions

File tree

internal/cli/flags/flag.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package flags
33

44
import (
55
"context"
6+
"errors"
67
"flag"
78
"io"
89
"strconv"
@@ -164,16 +165,20 @@ func (newFlag *Flag) Parse(args clihelper.Args, env map[string]string) error {
164165
return err
165166
}
166167

167-
const maxFlagsParse = 1000 // Maximum flags parse
168+
const maxFlagsParse = 1000
168169

169170
for range maxFlagsParse {
170171
err := flagSet.Parse(args)
171172
if err == nil {
172-
break
173+
return nil
173174
}
174175

175-
if errStr := err.Error(); !strings.HasPrefix(errStr, clihelper.ErrMsgFlagUndefined) {
176-
break
176+
// The set holds only this flag, so the loop skips flags owned by other
177+
// parsers instead of failing on them. That includes -h and --help, which
178+
// arrive as [flag.ErrHelp] rather than as an undefined-flag error.
179+
if !errors.Is(err, flag.ErrHelp) &&
180+
!strings.HasPrefix(err.Error(), clihelper.ErrMsgFlagUndefined) {
181+
return err
177182
}
178183

179184
args = flagSet.Args()

internal/cli/flags/flag_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package flags_test
22

33
import (
44
"bytes"
5+
"errors"
56
"flag"
67
"fmt"
78
"strings"
@@ -167,3 +168,84 @@ func TestFlag_Evaluate(t *testing.T) {
167168
})
168169
}
169170
}
171+
172+
func TestFlag_Parse(t *testing.T) {
173+
t.Parallel()
174+
175+
testCases := []struct {
176+
name string
177+
expected string
178+
args clihelper.Args
179+
expectedErr bool
180+
}{
181+
{
182+
name: "accepted value",
183+
args: clihelper.Args{"--level", "debug"},
184+
expected: "debug",
185+
},
186+
{
187+
name: "value rejected by the flag setter",
188+
args: clihelper.Args{"--level", "bogus"},
189+
expectedErr: true,
190+
},
191+
{
192+
name: "value missing",
193+
args: clihelper.Args{"--level"},
194+
expectedErr: true,
195+
},
196+
{
197+
name: "flag belonging to another parser",
198+
args: clihelper.Args{"--some-other-flag"},
199+
},
200+
{
201+
name: "value after a flag belonging to another parser",
202+
args: clihelper.Args{"--some-other-flag", "--level", "debug"},
203+
expected: "debug",
204+
},
205+
{
206+
name: "rejected value after a flag belonging to another parser",
207+
args: clihelper.Args{"--some-other-flag", "--level", "bogus"},
208+
expectedErr: true,
209+
},
210+
{
211+
name: "value after --help",
212+
args: clihelper.Args{"--help", "--level", "debug"},
213+
expected: "debug",
214+
},
215+
{
216+
name: "-h alone",
217+
args: clihelper.Args{"-h"},
218+
},
219+
}
220+
221+
for _, tc := range testCases {
222+
t.Run(tc.name, func(t *testing.T) {
223+
t.Parallel()
224+
225+
var got string
226+
227+
testFlag := flags.NewFlag(&clihelper.GenericFlag[string]{
228+
Name: "level",
229+
Setter: func(val string) error {
230+
if val == "bogus" {
231+
return errors.New("unsupported level")
232+
}
233+
234+
got = val
235+
236+
return nil
237+
},
238+
})
239+
240+
err := testFlag.Parse(tc.args, map[string]string{})
241+
242+
if tc.expectedErr {
243+
require.Error(t, err)
244+
return
245+
}
246+
247+
require.NoError(t, err)
248+
assert.Equal(t, tc.expected, got)
249+
})
250+
}
251+
}

0 commit comments

Comments
 (0)