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  }