From 4d1de9d5bad095dd935435aecdd8e6349e5e8f59 Mon Sep 17 00:00:00 2001 From: Ankit Hans Date: Tue, 29 Jun 2021 12:07:34 +0530 Subject: [PATCH] feat: merge default and user provided rulesconfig --- Makefile | 5 +- docs/src/validator/validator.md | 30 ++++++++- docs/src/validator/validator_length_rule.md | 20 ------ .../validator/validator_must_exist_rule.md | 15 ----- validator/example/cmd.go | 37 +++++++++++ validator/example/cmd_test.go | 39 +++++++++++ validator/rules/executor.go | 65 ++++++++++++++----- validator/rules/length.go | 5 +- validator/rules/must_exist.go | 3 +- validator/validator.go | 10 +-- 10 files changed, 162 insertions(+), 67 deletions(-) delete mode 100644 docs/src/validator/validator_length_rule.md delete mode 100644 docs/src/validator/validator_must_exist_rule.md create mode 100644 validator/example/cmd.go create mode 100644 validator/example/cmd_test.go diff --git a/Makefile b/Makefile index 0a4e0a8..39c4fc4 100644 --- a/Makefile +++ b/Makefile @@ -2,4 +2,7 @@ run: go run ./examples/host build: - go build ./examples/host \ No newline at end of file + go build ./examples/host + +test: + go test ./validator/example \ No newline at end of file diff --git a/docs/src/validator/validator.md b/docs/src/validator/validator.md index 382802a..c19921c 100644 --- a/docs/src/validator/validator.md +++ b/docs/src/validator/validator.md @@ -1,5 +1,5 @@ ## Validator -Validator can be used for testing or controlling many aspects of cobra commands. It provides many rules out of the box for validating the commands. +Validator can be used for testing and controlling many aspects of cobra commands. It provides many rules out of the box for validating the commands. ## Rules provided by validator - [LengthRule](validator_length_rule.md) @@ -7,3 +7,31 @@ Validator can be used for testing or controlling many aspects of cobra commands. - UseMatches > We are working on the validator library to provide more rules +## How to use +It is recommended to use the validator while writing unit tests for cobra commands. + +1. Create a validator of type `rules.RuleConfig`. You can provide your own RulesConfig or use the default one by leaving it empty +```go +var vali rules.RuleConfig +``` +or overriding default config +```go +var vali rules.RuleConfig = rules.RuleConfig{ + Verbose: true, + MustExist: rules.MustExist{ + Fields: []string{"Args"}, + }, +} +``` +2. Generate the validation errors by using `ExecuteRules` function over the config +```go +validationErr := vali.ExecuteRules(cmd) +``` +`ExecuteRules` function will return a slice of `ValidationError` object, which can be efficiently used for testing purposes. +```go +for _, errs := range validationErr { + if errs.Err != nil { + t.Errorf("%s: cmd %s: %s", errs.Rule, errs.Cmd.CommandPath(), errs.Name) + } +} +``` \ No newline at end of file diff --git a/docs/src/validator/validator_length_rule.md b/docs/src/validator/validator_length_rule.md deleted file mode 100644 index fbebf8a..0000000 --- a/docs/src/validator/validator_length_rule.md +++ /dev/null @@ -1,20 +0,0 @@ -## Length Rule -The lengths of properties in string format provided by `cobra.Command` can be controlled using the Length Rule. - -## How to use -1. Create a length rule by providing the limits for each attribute you want to control -```go -myLengthRule := &rules.Length{ - Limits: map[string]rules.Limit{ - "Use": {Min: 1, Max: 5}, - "Short": {Min: 4, Max: 10}, - "Long": {Min: 5, Max: 20}, - "Not": {Min: 2, Max: 22}, - }, -} -``` -2. Use ValidateLength function to validate your cobra command with the rule created above. It takes a verbose bool for enabling/disabling debug StatusLogs -```go -err := myLengthRule.ValidateLength(cmd, true) -``` -3. ValidateLength function will return a slice of `ValidationError` type, which will allow you to efficiently test your code \ No newline at end of file diff --git a/docs/src/validator/validator_must_exist_rule.md b/docs/src/validator/validator_must_exist_rule.md deleted file mode 100644 index 1f8892a..0000000 --- a/docs/src/validator/validator_must_exist_rule.md +++ /dev/null @@ -1,15 +0,0 @@ -## Must Exist Rule -This rule ensures that the fields specified in the rule must be present in the cobra command. - -## How to use -1. Create a MustExist rule by providing the fields you want to be present in cobra command -```go -myExistRule := &rules.MustPresent{ - Fields: []string{"Use", "Short", "Long", "Example", "SilenceUsage", "PreRun", "Hi"}, -} -``` -2. Use ValidateMustPresent function to validate your cobra command with the rule created above. It takes a verbose bool for enabling/disabling debug StatusLogs -```go -errr := myExistRule.ValidateMustPresent(cmd, true) -``` -3. ValidateMustPresent function will return a slice of `ValidationError` type, which will allow you to efficiently test your code \ No newline at end of file diff --git a/validator/example/cmd.go b/validator/example/cmd.go new file mode 100644 index 0000000..664afd0 --- /dev/null +++ b/validator/example/cmd.go @@ -0,0 +1,37 @@ +package example + +import ( + "github.com/spf13/cobra" +) + +func NewCommand() *cobra.Command { + + cmd := &cobra.Command{ + Use: "new", + Short: "This is the short", + Long: "This is long", + Example: "My Example", + RunE: func(cmd *cobra.Command, args []string) error { + return nil + }, + } + + cmd1 := &cobra.Command{ + Use: "subcmd", + Short: "", + Example: "examples", + Run: func(cmd *cobra.Command, args []string) {}, + } + + cmd2 := &cobra.Command{ + Use: "subcmd2", + Short: "", + Example: "examples", + Run: func(cmd *cobra.Command, args []string) {}, + } + + cmd1.AddCommand(cmd2) + cmd.AddCommand(cmd1) + + return cmd +} diff --git a/validator/example/cmd_test.go b/validator/example/cmd_test.go new file mode 100644 index 0000000..8261975 --- /dev/null +++ b/validator/example/cmd_test.go @@ -0,0 +1,39 @@ +package example + +import ( + "bytes" + "io/ioutil" + "testing" + + "github.com/aerogear/charmil/validator/rules" +) + +func Test_ExecuteCommand(t *testing.T) { + cmd := NewCommand() + + // Testing cobra commands with default recommended config + // default config can also be overrided by + /* + var vali rules.RuleConfig = rules.RuleConfig{ + Verbose: true, + MustExist: rules.MustExist{ + Fields: []string{"Args"}, + }, + } + */ + var vali rules.RuleConfig + validationErr := vali.ExecuteRules(cmd) + for _, errs := range validationErr { + if errs.Err != nil { + t.Errorf("%s: cmd %s: %s", errs.Rule, errs.Cmd.CommandPath(), errs.Name) + } + } + + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.Execute() + _, err := ioutil.ReadAll(b) + if err != nil { + t.Fatal(err) + } +} diff --git a/validator/rules/executor.go b/validator/rules/executor.go index e5ae125..0245b28 100644 --- a/validator/rules/executor.go +++ b/validator/rules/executor.go @@ -1,27 +1,28 @@ package rules import ( + "encoding/json" + "log" + "github.com/aerogear/charmil/validator" "github.com/spf13/cobra" ) -// A rule executor which will execute the selected rules -// for now all rules!! -// user API is like this -/* - validationErrors := validator.executeRules(cmd) -*/ - +// RuleConfig is the struct that stores +// configuration of rules type RuleConfig struct { Verbose bool Length MustExist } +// ExecuteRules executes all the rules +// according to the RuleConfig provided func (config *RuleConfig) ExecuteRules(cmd *cobra.Command) []validator.ValidationError { var errors []validator.ValidationError info := validator.StatusLog{TotalTested: 0, TotalErrors: 0, Errors: errors} + // initialize default rules config.initDefaultRules() return config.executeHelper(cmd, info) @@ -33,7 +34,6 @@ func (config *RuleConfig) executeHelper(cmd *cobra.Command, info validator.Statu cmd, config.Verbose, info, - config, func(cmd *cobra.Command, verbose bool) []validator.ValidationError { info.Errors = append(info.Errors, validateLength(cmd, config.Length, config.Verbose)...) info.Errors = append(info.Errors, validateMustExist(cmd, config.MustExist, config.Verbose)...) @@ -42,17 +42,48 @@ func (config *RuleConfig) executeHelper(cmd *cobra.Command, info validator.Statu ) } +// initDefaultRules initialize default rules +// and overrides the default rules if RuleConfig is provided by the user func (config *RuleConfig) initDefaultRules() { - if config.Length.Enable && config.Length.Limits == nil { - config.Length.Limits = map[string]Limit{ - "Use": {Min: 2, Max: 20}, - "Short": {Min: 15, Max: 200}, - "Long": {Min: 100, Max: 10000}, - "Example": {Min: 100, Max: 10000}, - } + + // default config for rules + var defaultConfig = &RuleConfig{ + Verbose: false, + Length: Length{ + Limits: map[string]Limit{ + "Use": {Min: 2, Max: 20}, + "Short": {Min: 15, Max: 200}, + "Long": {Min: 100, Max: 10000}, + "Example": {Min: 100, Max: 10000}, + }, + }, + MustExist: MustExist{ + Fields: []string{"Use", "Short", "Long", "Example"}, + }, } - if config.MustExist.Enable && config.MustExist.Fields == nil { - config.MustExist.Fields = []string{"Use", "Short", "Long", "Example"} + // Check verbose input from user + var verbose bool + if config != nil && config.Verbose { + verbose = true } + + // Set Config to defaultConfig + *config = *defaultConfig + config.Verbose = verbose + + // Merge the defaultConfig and Config given by user + if config.Length.Limits != nil && config.MustExist.Fields != nil { + out, err := json.Marshal(config) + if err != nil { + log.Fatal(err) + } + data := []byte(out) + + errr := json.Unmarshal(data, &defaultConfig) + if errr != nil { + log.Fatal(errr) + } + } + } diff --git a/validator/rules/length.go b/validator/rules/length.go index 8c091d7..8d9546c 100644 --- a/validator/rules/length.go +++ b/validator/rules/length.go @@ -25,7 +25,6 @@ var LengthRule = "LENGTH_RULE" // with key as attribute for which length is controlled // and value limit as Limit struct type Length struct { - Enable bool Limits map[string]Limit } @@ -73,10 +72,10 @@ func validateField(cmd *cobra.Command, limit Limit, value string, path string, f } if length < limit.Min { - return validator.ValidationError{Name: fmt.Sprintf("%s length should be at least %d in %s cmd", fieldName, limit.Min, path), Err: ErrLengthMin, Rule: LengthRule, Cmd: cmd} + return validator.ValidationError{Name: fmt.Sprintf("%s length should be at least %d", fieldName, limit.Min), Err: ErrLengthMin, Rule: LengthRule, Cmd: cmd} } if length > limit.Max { - return validator.ValidationError{Name: fmt.Sprintf("%s length should be less than %d in %s cmd", fieldName, limit.Max, path), Err: ErrLengthMax, Rule: LengthRule, Cmd: cmd} + return validator.ValidationError{Name: fmt.Sprintf("%s length should be less than %d", fieldName, limit.Max), Err: ErrLengthMax, Rule: LengthRule, Cmd: cmd} } return validator.ValidationError{} diff --git a/validator/rules/must_exist.go b/validator/rules/must_exist.go index d64f972..1f678c5 100644 --- a/validator/rules/must_exist.go +++ b/validator/rules/must_exist.go @@ -20,7 +20,6 @@ var MustExistRule = "MUST_EXIST_RULE" // MustExist is a struct that provides // Fields defined for MustExist validation type MustExist struct { - Enable bool Fields []string } @@ -60,7 +59,7 @@ func validateByType(cmd *cobra.Command, reflectValue *reflect.Value, field strin (reflectValue.Kind().String() == "int" && reflectValue.Int() == 0) || (reflectValue.Kind().String() == "slice" && reflectValue.Len() == 0) || (reflectValue.Kind().String() == "map" && reflectValue.Len() == 0) { - errors = append(errors, validator.ValidationError{Name: fmt.Sprintf("%s must be present in %s cmd", field, path), Err: ErrMustExistAbsent, Rule: MustExistRule, Cmd: cmd}) + errors = append(errors, validator.ValidationError{Name: fmt.Sprintf("%s must be present", field), Err: ErrMustExistAbsent, Rule: MustExistRule, Cmd: cmd}) } return errors diff --git a/validator/validator.go b/validator/validator.go index d25eea7..558375c 100644 --- a/validator/validator.go +++ b/validator/validator.go @@ -6,12 +6,6 @@ import ( "github.com/spf13/cobra" ) -// Rule is the interface which is implemented -// by every rule defined in validator package -type Rule interface { - ExecuteRules(cmd *cobra.Command) []ValidationError -} - // ValidationError is a default validation error type ValidationError struct { Name string @@ -33,7 +27,7 @@ type StatusLog struct { // Traverse is used to traverse and validate // the command and it's descendant commands -func Traverse(cmd *cobra.Command, verbose bool, info StatusLog, x interface{}, validate func(cmd *cobra.Command, verbose bool) []ValidationError) []ValidationError { +func Traverse(cmd *cobra.Command, verbose bool, info StatusLog, validate func(cmd *cobra.Command, verbose bool) []ValidationError) []ValidationError { // validate the root command err := validate(cmd, verbose) // record stats @@ -50,7 +44,7 @@ func Traverse(cmd *cobra.Command, verbose bool, info StatusLog, x interface{}, v } // recursive call for ValidateHelper - if err := Traverse(child, verbose, info, x, validate); err != nil { + if err := Traverse(child, verbose, info, validate); err != nil { return err } }