github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/interactive/metaquery/validators.go (about) 1 package metaquery 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/turbot/go-kit/helpers" 8 "github.com/turbot/steampipe/pkg/cmdconfig" 9 "github.com/turbot/steampipe/pkg/constants" 10 "github.com/turbot/steampipe/pkg/utils" 11 "golang.org/x/text/cases" 12 "golang.org/x/text/language" 13 ) 14 15 // ValidationResult :: response for Validate 16 type ValidationResult struct { 17 Err error 18 Message string 19 ShouldRun bool 20 } 21 22 type validator func(val []string) ValidationResult 23 24 // Validate :: validate a full metaquery along with arguments - we can return err & validationResult 25 func Validate(query string) ValidationResult { 26 query = strings.TrimSuffix(query, ";") 27 // get the meta query 28 cmd, args := getCmdAndArgs(query) 29 30 validatorFunction := metaQueryDefinitions[cmd].validator 31 32 if validatorFunction != nil { 33 return validatorFunction(args) 34 } 35 return ValidationResult{Err: fmt.Errorf("'%s' is not a known command", query)} 36 } 37 38 func titleSentenceCase(title string) string { 39 caser := cases.Title(language.English) 40 titleSegments := strings.SplitN(title, "-", 2) 41 if len(titleSegments) == 1 { 42 return caser.String(title) 43 } 44 titleSegments = []string{caser.String(titleSegments[0]), titleSegments[1]} 45 return strings.Join(titleSegments, "-") 46 } 47 48 func booleanValidator(metaquery string, validators ...validator) validator { 49 return func(args []string) ValidationResult { 50 // Error: argument required multi-line mode is off. You can enable it with: .multi on 51 // headers mode is off. You can enable it with: .headers on 52 // timing mode is off. You can enable it with: .timing on 53 title := titleSentenceCase(metaQueryDefinitions[metaquery].title) 54 numArgs := len(args) 55 56 if numArgs == 0 { 57 // get the current status of this mode (convert metaquery name into arg name) 58 // NOTE - request second arg from cast even though we donl;t use it - to avoid panic 59 currentStatus := cmdconfig.Viper().GetBool(constants.ArgFromMetaquery(metaquery)) 60 // what is the new status (the opposite) 61 newStatus := !currentStatus 62 63 // convert current and new status to on/off 64 currentStatusString := constants.BoolToOnOff(currentStatus) 65 newStatusString := constants.BoolToOnOff(newStatus) 66 67 // what is the action to get to the new status 68 actionString := constants.BoolToEnableDisable(newStatus) 69 70 return ValidationResult{ 71 Message: fmt.Sprintf(`%s mode is %s. You can %s it with: %s.`, 72 title, 73 constants.Bold(currentStatusString), 74 actionString, 75 constants.Bold(fmt.Sprintf("%s %s", metaquery, newStatusString))), 76 } 77 } 78 if numArgs > 1 { 79 return ValidationResult{ 80 Err: fmt.Errorf("command needs 1 argument - got %d", numArgs), 81 } 82 } 83 return buildValidationResult(args, validators) 84 } 85 } 86 87 func composeValidator(validators ...validator) validator { 88 return func(val []string) ValidationResult { 89 return buildValidationResult(val, validators) 90 } 91 } 92 93 func validatorFromArgsOf(cmd string) validator { 94 return func(val []string) ValidationResult { 95 metaQueryDefinition := metaQueryDefinitions[cmd] 96 validArgs := []string{} 97 98 for _, validArg := range metaQueryDefinition.args { 99 validArgs = append(validArgs, validArg.value) 100 } 101 102 return allowedArgValues(false, validArgs...)(val) 103 } 104 } 105 106 var atLeastNArgs = func(n int) validator { 107 return func(args []string) ValidationResult { 108 numArgs := len(args) 109 if numArgs < n { 110 return ValidationResult{ 111 Err: fmt.Errorf("command needs at least %d %s - got %d", n, utils.Pluralize("argument", n), numArgs), 112 } 113 } 114 return ValidationResult{ShouldRun: true} 115 } 116 } 117 118 var atMostNArgs = func(n int) validator { 119 return func(args []string) ValidationResult { 120 numArgs := len(args) 121 if numArgs > n { 122 return ValidationResult{ 123 Err: fmt.Errorf("command needs at most %d %s - got %d", n, utils.Pluralize("argument", n), numArgs), 124 } 125 } 126 return ValidationResult{ShouldRun: true} 127 } 128 } 129 130 var exactlyNArgs = func(n int) validator { 131 return func(args []string) ValidationResult { 132 numArgs := len(args) 133 if numArgs != n { 134 return ValidationResult{ 135 Err: fmt.Errorf("command needs %d %s - got %d", n, utils.Pluralize("argument", n), numArgs), 136 } 137 } 138 return ValidationResult{ 139 ShouldRun: true, 140 } 141 } 142 } 143 144 var noArgs = exactlyNArgs(0) 145 146 var allowedArgValues = func(caseSensitive bool, allowedValues ...string) validator { 147 return func(args []string) ValidationResult { 148 if !caseSensitive { 149 // convert everything to lower case 150 for idx, a := range args { 151 args[idx] = strings.ToLower(a) 152 } 153 for idx, av := range allowedValues { 154 allowedValues[idx] = strings.ToLower(av) 155 } 156 } 157 158 for _, arg := range args { 159 if !helpers.StringSliceContains(allowedValues, arg) { 160 return ValidationResult{ 161 Err: fmt.Errorf("valid values for this command are %v - got %s", allowedValues, arg), 162 } 163 } 164 } 165 return ValidationResult{ShouldRun: true} 166 } 167 } 168 169 func buildValidationResult(val []string, validators []validator) ValidationResult { 170 var messages string 171 for _, v := range validators { 172 validate := v(val) 173 if validate.Message != "" { 174 messages = fmt.Sprintf("%s\n%s", messages, validate.Message) 175 } 176 if validate.Err != nil { 177 return ValidationResult{ 178 Err: validate.Err, 179 Message: messages, 180 } 181 } 182 if !validate.ShouldRun { 183 return ValidationResult{ 184 Message: messages, 185 ShouldRun: false, 186 } 187 } 188 } 189 return ValidationResult{ 190 Message: messages, 191 ShouldRun: true, 192 } 193 }