Skip to content

Commit a82c397

Browse files
authored
Merge pull request #81 from Icinga/config-qa
Also validate that arguments to `config` functions are struct pointers
2 parents ab3632f + d70e13d commit a82c397

File tree

2 files changed

+33
-25
lines changed

2 files changed

+33
-25
lines changed

config/config.go

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ import (
5757

5858
// ErrInvalidArgument is the error returned by [ParseFlags] or [FromYAMLFile] if
5959
// its parsing result cannot be stored in the value pointed to by the designated passed argument which
60-
// must be a non-nil pointer.
60+
// must be a non-nil struct pointer.
6161
var ErrInvalidArgument = stderrors.New("invalid argument")
6262

6363
// FromYAMLFile parses the given YAML file and stores the result
64-
// in the value pointed to by v. If v is nil or not a pointer,
64+
// in the value pointed to by v. If v is nil or not a struct pointer,
6565
// FromYAMLFile returns an [ErrInvalidArgument] error.
6666
// It is possible to define default values via the struct tag `default`.
6767
// The function also validates the configuration using the Validate method
@@ -91,12 +91,11 @@ var ErrInvalidArgument = stderrors.New("invalid argument")
9191
// // ...
9292
// }
9393
func FromYAMLFile(name string, v Validator) error {
94-
rv := reflect.ValueOf(v)
95-
if rv.Kind() != reflect.Pointer || rv.IsNil() {
96-
return errors.Wrapf(ErrInvalidArgument, "non-nil pointer expected, got %T", v)
94+
if err := validateNonNilStructPointer(v); err != nil {
95+
return errors.WithStack(err)
9796
}
9897

99-
// #nosec G304 -- Potential file inclusion via variable - Its purpose is to load any file name that is passed to it, so doesn't need to validate anything.
98+
// #nosec G304 -- Accept user-controlled input for config file.
10099
f, err := os.Open(name)
101100
if err != nil {
102101
return errors.Wrap(err, "can't open YAML file "+name)
@@ -125,11 +124,10 @@ func FromYAMLFile(name string, v Validator) error {
125124
type EnvOptions = env.Options
126125

127126
// FromEnv parses environment variables and stores the result in the value pointed to by v.
128-
// If v is nil or not a pointer, FromEnv returns an [ErrInvalidArgument] error.
127+
// If v is nil or not a struct pointer, FromEnv returns an [ErrInvalidArgument] error.
129128
func FromEnv(v Validator, options EnvOptions) error {
130-
rv := reflect.ValueOf(v)
131-
if rv.Kind() != reflect.Ptr || rv.IsNil() {
132-
return errors.Wrapf(ErrInvalidArgument, "non-nil pointer expected, got %T", v)
129+
if err := validateNonNilStructPointer(v); err != nil {
130+
return errors.WithStack(err)
133131
}
134132

135133
if err := defaults.Set(v); err != nil {
@@ -148,7 +146,7 @@ func FromEnv(v Validator, options EnvOptions) error {
148146
}
149147

150148
// ParseFlags parses CLI flags and stores the result
151-
// in the value pointed to by v. If v is nil or not a pointer,
149+
// in the value pointed to by v. If v is nil or not a struct pointer,
152150
// ParseFlags returns an [ErrInvalidArgument] error.
153151
// ParseFlags adds a default Help Options group,
154152
// which contains the options -h and --help.
@@ -172,17 +170,16 @@ func FromEnv(v Validator, options EnvOptions) error {
172170
// // ...
173171
// }
174172
func ParseFlags(v any) error {
175-
rv := reflect.ValueOf(v)
176-
if rv.Kind() != reflect.Pointer || rv.IsNil() {
177-
return errors.Wrapf(ErrInvalidArgument, "non-nil pointer expected, got %T", v)
173+
if err := validateNonNilStructPointer(v); err != nil {
174+
return errors.WithStack(err)
178175
}
179176

180177
parser := flags.NewParser(v, flags.Default^flags.PrintErrors)
181178

182179
if _, err := parser.Parse(); err != nil {
183180
var flagErr *flags.Error
184-
if errors.As(err, &flagErr) && flagErr.Type == flags.ErrHelp {
185-
fmt.Fprintln(os.Stdout, flagErr)
181+
if errors.As(err, &flagErr) && errors.Is(flagErr.Type, flags.ErrHelp) {
182+
_, _ = fmt.Fprintln(os.Stdout, flagErr)
186183
os.Exit(0)
187184
}
188185

@@ -191,3 +188,14 @@ func ParseFlags(v any) error {
191188

192189
return nil
193190
}
191+
192+
// validateNonNilStructPointer checks if the provided value is a non-nil pointer to a struct.
193+
// It returns an error if the value is not a pointer, is nil, or does not point to a struct.
194+
func validateNonNilStructPointer(v any) error {
195+
rv := reflect.ValueOf(v)
196+
if rv.Kind() != reflect.Pointer || rv.IsNil() || rv.Elem().Kind() != reflect.Struct {
197+
return errors.Wrapf(ErrInvalidArgument, "non-nil struct pointer expected, got %T", v)
198+
}
199+
200+
return nil
201+
}

config/config_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -220,10 +220,7 @@ func TestFromEnv(t *testing.T) {
220220
var config nonStructValidator
221221

222222
err := FromEnv(&config, EnvOptions{})
223-
// Struct pointer assertion is done in the defaults library,
224-
// so we must ensure that the error returned is not one of our own errors.
225-
require.NotErrorIs(t, err, ErrInvalidArgument)
226-
require.NotErrorIs(t, err, errInvalidConfiguration)
223+
require.ErrorIs(t, err, ErrInvalidArgument)
227224
})
228225
}
229226

@@ -299,11 +296,7 @@ func TestFromYAMLFile(t *testing.T) {
299296
var config nonStructValidator
300297

301298
err := FromYAMLFile(file.Name(), &config)
302-
require.Error(t, err)
303-
// Struct pointer assertion is done in the defaults library,
304-
// so we must ensure that the error returned is not one of our own errors.
305-
require.NotErrorIs(t, err, ErrInvalidArgument)
306-
require.NotErrorIs(t, err, errInvalidConfiguration)
299+
require.ErrorIs(t, err, ErrInvalidArgument)
307300
})
308301
})
309302

@@ -362,6 +355,13 @@ func TestParseFlags(t *testing.T) {
362355
require.ErrorIs(t, err, ErrInvalidArgument)
363356
})
364357

358+
t.Run("Non-struct pointer argument", func(t *testing.T) {
359+
var flags int
360+
361+
err := ParseFlags(&flags)
362+
require.ErrorIs(t, err, ErrInvalidArgument)
363+
})
364+
365365
t.Run("Exit on help flag", func(t *testing.T) {
366366
// This test case checks the behavior of ParseFlags() when the help flag (e.g. -h) is provided.
367367
// Since ParseFlags() calls os.Exit() upon encountering the help flag, we need to run this

0 commit comments

Comments
 (0)