Skip to content

Commit

Permalink
feat: merge default and user provided rulesconfig
Browse files Browse the repository at this point in the history
  • Loading branch information
ankithans authored and wtrocki committed Jun 29, 2021
1 parent 71d4be6 commit 4d1de9d
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 67 deletions.
5 changes: 4 additions & 1 deletion Makefile
Expand Up @@ -2,4 +2,7 @@ run:
go run ./examples/host

build:
go build ./examples/host
go build ./examples/host

test:
go test ./validator/example
30 changes: 29 additions & 1 deletion docs/src/validator/validator.md
@@ -1,9 +1,37 @@
## 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)
- [MustExistRule](validator_must_exist_rule.md)
- 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)
}
}
```
20 changes: 0 additions & 20 deletions docs/src/validator/validator_length_rule.md

This file was deleted.

15 changes: 0 additions & 15 deletions docs/src/validator/validator_must_exist_rule.md

This file was deleted.

37 changes: 37 additions & 0 deletions 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
}
39 changes: 39 additions & 0 deletions 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)
}
}
65 changes: 48 additions & 17 deletions 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)
Expand All @@ -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)...)
Expand All @@ -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)
}
}

}
5 changes: 2 additions & 3 deletions validator/rules/length.go
Expand Up @@ -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
}

Expand Down Expand Up @@ -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{}
Expand Down
3 changes: 1 addition & 2 deletions validator/rules/must_exist.go
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down
10 changes: 2 additions & 8 deletions validator/validator.go
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
}
}
Expand Down

0 comments on commit 4d1de9d

Please sign in to comment.