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
feat: validator for cobra cmds #82
Conversation
I think I have provided info for that in issues and chat. Basically the best would be for this to be completely separate library. |
I would really love us to come with rules that can declare the presence of the field. Length of the field etc. I would love to see this done this week so we can integrate that with the RHOAS CLI next week as unit test or whatever you figure out. |
examples/plugins/date/main.go
Outdated
@@ -49,5 +53,11 @@ func DateCommand() (*cobra.Command, error) { | |||
}, | |||
} | |||
|
|||
// default validation provided by validator package | |||
validationErr := validator.Validate(cmd) |
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.
Yes.. while this is ok. It makes no sense.
Validation is development feature but we are using it at runtime. This needs to change.
validator/std_validator.go
Outdated
return nil | ||
} | ||
|
||
// ValidateCustom will allow user to |
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.
This is wrong. Every rule should be custom rule. Create interface for rule with single method that validates single command and then traverse thru nested structure. This is easier than current architecture and will allow anyone to drop any rules - users will always need to define rules somehow or override them, but it should not require to actually write them - for example fieldExist or string length rule needs to have some arguments to be defined.
validator/std_validator.go
Outdated
func handleDefaultErrors(cmd *cobra.Command) error { | ||
cmdPath := cmd.CommandPath() | ||
|
||
if len(cmd.Use) < 1 { |
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.
Makes no sense in number of dimensions:
- we are missing exact location that can be used to pinpoint file that causing issues
- we are returning only single issue (rather than numerous issues during linting)
- Hardcoded values.
please rebase conflicts. I have done some investigations and I feel like unit testing framework with assertions could work, but number of rules we would be able to pull will be limited to structure checks. I think that is fine - tried to do AST linter and while you can do the same - it is way harder to write rules |
@wtrocki conflicts resolved |
how will the user add the custom rules. Will he be writing rules with AST? |
No AST. Lets finalize this PR properly using approach we agred on. We need to lower expectations to adjust to progress. AST linter is 3-4 times more work and our use cases are simple enough to use it without ast/static code analisis |
create golang interface representing rule and build number of rules based on them. Users could add their own. No AST |
78e7f12
to
e549448
Compare
@ankithans I think it will be good to have class and sequence diagram (can be on piece of paper) to visualize what we want to do. |
validator/length.go
Outdated
cmdPath := cmd.CommandPath() | ||
|
||
use := cmd.Use | ||
useErr := validateField(l.Use, use, cmdPath) |
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.
This part might bring some maintenance challenges and breaks DRY rule.
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.
We can use reflect package to get values by strings there is no need to repeat them
validator/length.go
Outdated
) | ||
|
||
type LengthRule struct { | ||
Use Limit |
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.
While this is clever I think it restricts us how rules are build.
My take is that rule:
- Has name
- Has Function that does something - Doesn't have any fields
- Has specific arguments that configure how rule works.
In this case name is defined as Struct but I think we also need name as constant.
this way we can build LengthRule
with dictionary of key values, where key could be cobra structure key like Use
and values might be functions, min, max etc.
LengthRule(Arguments{
"Use": Limit{3,10},
"Short": Limit{3,10},
})
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.
edit : ignore this
type LengthRuleMap map[string]Limit // instead of struct
// user
var r1 validator.Rule = &validator.LengthRuleMap{
"Use": validator.Limit{Min: 1, Max: 5},
"Short": validator.Limit{Min: 4, Max: 5},
"Long": validator.Limit{Min: 5, Max: 20},
"Example": validator.Limit{Min: 5, Max: 30},
}
yes this helps in traversing and don't break DRY
here we will need to validate the key
in dict, to map with cobra command struct
examples/plugins/date/main.go
Outdated
validationErr := validator.Validate(cmd) | ||
if validationErr != nil { | ||
return nil, validationErr | ||
var r validator.Rule = &validator.LengthRule{ |
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.
This approach as number of drawbacks:
- it is fixed to the point where we need to redeclare every field
- cannot be declarative
- Doesn't provide us base for adding other rules (missing interface for rule)
Amazing work! This is getting closer - but still needs to be refined and we need more rules. Nice to see some non trivial code happening |
Rough example showing what I meant by using reflection: |
@aerogear/charmil-core
Thanks for this!! reflect is great 🙌 |
lets start with those two rules for now |
validator/length.go
Outdated
err := validateLength(cmd, l) | ||
errors = append(errors, err...) | ||
|
||
for _, child := range cmd.Commands() { |
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.
we are doing only one level of commands. we should check all commands including children of children
validator/length.go
Outdated
var errors []error | ||
|
||
for fieldName, limits := range l.Limits { | ||
reflectValue := reflect.ValueOf(cmd).Elem().FieldByName(fieldName).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.
no error handling/input validation
validator/length.go
Outdated
} | ||
|
||
if length < limit.Min { | ||
return fmt.Errorf("%s in %s: length should be atleast %d", value, path, limit.Min) |
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.
typo atleast
validator/length.go
Outdated
return true, fmt.Errorf("max and min must be greater than 0") | ||
} | ||
if limit.Max == 0 && limit.Min == 0 { | ||
return false, fmt.Errorf("limit not 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.
lack of good message patterns.
Limit to what is not set? Where?
Would use rule name for all rule specific errors.
validator/length.go
Outdated
} | ||
|
||
func validateField(limit Limit, value string, path string) error { | ||
length := len(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.
Lets add verbose mode that prints debug info for each rule cmd field
rebase with main
One comment missed |
rebase
4373863
to
08d3ccc
Compare
done @wtrocki |
Merged with 101 comments: |
"one-oh-one" 😅🥲 |
Btw.. This is pretty much still work in progress change in main. Let's work on refining it better. |
Priority nr1 is to do rhoas integration. |
@ankithans I just worked with the validator functionality for the first and it looks really nice just had a few questions on going forward on what we are looking to refine.
|
I think, it's better to be part of rule, which can be run by ruleExecutor. As every rule will have some checking like this. So if we do that in ruleExecutor, things might get messed up. WDYS?
Yep thanks for suggesting, need to document again |
Yes that being in the validator would be annoying but I don't think it has to be. I am gonna make a quick pr today on what think we can do as I think I have a good idea. We also have to be mindful of yaml/json configs for the rules. But I will get back to you. |
Methods
Validates the command and it's descendants
func Validate(cmd *cobra.Command) error
// handles default errors declared in validator packagefunc ValidateCustom(cmd *cobra.Command, handleErrors func() error) error
// on the top of default errors, user can define custom errorsTo decide
@aerogear/charmil-core
closes #58