-
Notifications
You must be signed in to change notification settings - Fork 903
feat: add cli first class validation #8374
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
Conversation
@@ -23,6 +23,10 @@ const ( | |||
type Option struct { | |||
Name string `json:"name,omitempty"` | |||
Description string `json:"description,omitempty"` | |||
// Required means this value must be set by some means. It requires | |||
// `ValueSource != ValueSourceNone` | |||
// If `Default` is set, then `Required` is ignored. |
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.
Why not make Required a composable Validation rule instead?
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 cannot be a validation rule because the validation rule is only called on Set()
. If the user never provides a value, and no default is set, then Set()
is never called.
So it has to happen in the flag parsing logic.
@@ -16,6 +16,49 @@ import ( | |||
"gopkg.in/yaml.v3" | |||
) | |||
|
|||
// Validator is a wrapper around a pflag.Value that allows for validation | |||
// of the value after or before it has been set. |
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.
I like that this implements pflag.Value
.
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.
Maybe there's a function Required[T any]() ValidateFunc[T]
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.
Maybe there's a function
Required[T any]() ValidateFunc[T]
I was trying, but unfortunately Validate()
can only validate inputs. If there is no input, Set()
is never called for this to trigger on.
The only way I could think of making it work is have some post-processing step for the option? It felt easier to make it a flag on the Options and just touch it at the flag parsing.
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.
I think it's a common enough validation requirement that making it apart of the Option field is justified.
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.
Agreed. The downside is that if Default
is set, then Required
is ignored. Maybe we should throw an error somewhere if you configure an option like that.
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.
I like the improvement and idea. Well done!
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.
I love how nicely you plugged in this feature without much extra code.
I did notice a slight inconsistency though:
Required: true

Required: false

With required flags, the actual error is lost and the message becomes incorrect (the value isn't missing, just didn't pass validation).
(I simply played around a bit by modifying an option in config-ssh, picked randomly)
{
Flag: "ssh-option",
FlagShorthand: "o",
Env: "CODER_SSH_CONFIG_OPTS",
Description: "Specifies additional SSH options to embed in each host stanza.",
Required: true,
Value: clibase.Validate(clibase.StringArrayOf(&sshConfigOpts.sshOptions), func(value *clibase.StringArray) error {
for _, v := range *value {
if !strings.Contains(v, " ") {
return xerrors.Errorf("ssh option %q must contain a space", v)
}
}
return nil
}),
},
cli/clibase/values.go
Outdated
validate func(T) error | ||
} | ||
|
||
func Validate[T pflag.Value](opt T, validate ...func(value T) error) *Validator[T] { |
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.
What's the use-case for variadic validate funcs? Do we foresee passing multiple funcs? Typically I'm all for it, but with generics it seems to hurt VSCode autocomplete-ability (autocompletes to []func
😒).
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.
Eww on the autocomplete.
The only rational was a comment by @johnstcn that it might make sense to create building block style validators that you could reuse. But I can put it back to a regular, and then we can create a CombineValidator
or something that allows combining.
I had no strong opinions either way.
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.
Huh my autocomplete works fine in Goland. Still we can make the varadic some helper func. Or make it a variadic when we need that
This is a really good catch thanks. I moved the Required check too far up the command run stack. After this PR I think some of that code should be refactored to accumulate all errors and make nicer error messages.
|
Added a unit test for the invalid case with a required field |
What this does
A standard way to validate cli flags.
Problem
Validating cli flags tends to be in the cli handler body. This just adds some extra
if !valid { return err }
type code blocks that add length to the handlers. We also doif flag == "" { return xerrors.Errorf("<flag> must be specified")}
errors.These validation & required errors are not standardized. So the messaging & display of these errors really depends on how much care the developer put in at the time.
This PR aims to standardize how we handle cli flag validation and "require" such that all returned errors will be standard and ideally all errors are returned in a group if possible (this needs to get better on the cmd handling).
Future work
Make the returned standard errors pretty.
Example
An example of requiring a value be set, and adding validation to said value.
Before
After