Repository: AlecAivazis/survey
Pull request #362 (2021-07-03 23:40:50)

Validators for the number of responses in multiselect
In order to have a more complete API and remove worries to the user, I propose to add a series of validators that can make things easier for the user, at this moment I only propose two very simple validators to validate the maximum and minimum of selected answers in a multiselect.

I would like to know what do you think about the proposal and if it is possible to make more validators that can be useful. 

Personally I would like to have a number of validators similar to the [Pydantic](https://pydantic-docs.helpmanual.io) library of python.

AlecAivazis: Sorry i haven't gotten to this yet @MAD-py. work was really busy this week, will try to find some time to look at it over the weekend


MAD-py: @AlecAivazis no problem. Actually I had a pretty busy week too.
MAD-py: hi @AlecAivazis 

I've already added the validators info in the READM, I'll be working on more validators that are useful for the API and in a while I'll be doing the corresponding PR.
AlecAivazis: Thanks for doing that! I'll merge this in once github gives us the 👍 

Patch:
diff --git a/README.md b/README.md
index 75f323e..dad8f2d 100644
--- a/README.md
+++ b/README.md
@@ -347,6 +347,8 @@ validators include:
 | Required     | any         | Rejects zero values of the response type                    | Boolean values pass straight through since the zero value (false) is a valid response |
 | MinLength(n) | string      | Enforces that a response is at least the given length       |                                                                                       |
 | MaxLength(n) | string      | Enforces that a response is no longer than the given length |                                                                                       |
+| MaxItems(n)  | []OptionAnswer | Enforces that a response has no more selections of the indicated |                                                                               |
+| MinItems(n)  | []OptionAnswer | Enforces that a response has no less selections of the indicated |                                                                               |
 
 ## Help Text
 
diff --git a/validate.go b/validate.go
index ba7e127..462dc5e 100644
--- a/validate.go
+++ b/validate.go
@@ -4,6 +4,8 @@ import (
 	"errors"
 	"fmt"
 	"reflect"
+
+	"github.com/AlecAivazis/survey/v2/core"
 )
 
 // Required does not allow an empty value
@@ -58,6 +60,44 @@ func MinLength(length int) Validator {
 	}
 }
 
+// MaxItems requires that the list is no longer than the specified value
+func MaxItems(numberItems int) Validator {
+	// return a validator that checks the length of the list
+	return func(val interface{}) error {
+		if list, ok := val.([]core.OptionAnswer); ok {
+			// if the list is longer than the given value
+			if len(list) > numberItems {
+				// yell loudly
+				return fmt.Errorf("value is too long. Max items is %v", numberItems)
+			}
+		} else {
+			// otherwise we cannot convert the value into a list of answer and cannot enforce length
+			return fmt.Errorf("cannot impose the length on something other than a list of answers")
+		}
+		// the input is fine
+		return nil
+	}
+}
+
+// MinItems requires that the list is longer or equal in length to the specified value
+func MinItems(numberItems int) Validator {
+	// return a validator that checks the length of the list
+	return func(val interface{}) error {
+		if list, ok := val.([]core.OptionAnswer); ok {
+			// if the list is shorter than the given value
+			if len(list) < numberItems {
+				// yell loudly
+				return fmt.Errorf("value is too long. Min items is %v", numberItems)
+			}
+		} else {
+			// otherwise we cannot convert the value into a list of answer and cannot enforce length
+			return fmt.Errorf("cannot impose the length on something other than a list of answers")
+		}
+		// the input is fine
+		return nil
+	}
+}
+
 // ComposeValidators is a variadic function used to create one validator from many.
 func ComposeValidators(validators ...Validator) Validator {
 	// return a validator that calls each one sequentially


Test patch:
diff --git a/validate_test.go b/validate_test.go
index 4def869..62c4c56 100644
--- a/validate_test.go
+++ b/validate_test.go
@@ -3,6 +3,8 @@ package survey
 import (
 	"math/rand"
 	"testing"
+
+	"github.com/AlecAivazis/survey/v2/core"
 )
 
 func TestRequired_canSucceedOnPrimitiveTypes(t *testing.T) {
@@ -86,6 +88,40 @@ func randString(n int) string {
 	return string(b)
 }
 
+func TestMaxItems(t *testing.T) {
+	// the list to test
+	testList := []core.OptionAnswer{
+		core.OptionAnswer{Value: "a", Index: 0},
+		core.OptionAnswer{Value: "b", Index: 1},
+		core.OptionAnswer{Value: "c", Index: 2},
+		core.OptionAnswer{Value: "d", Index: 3},
+		core.OptionAnswer{Value: "e", Index: 4},
+		core.OptionAnswer{Value: "f", Index: 5},
+	}
+
+	// validate the list
+	if err := MaxItems(4)(testList); err == nil {
+		t.Error("No error returned with input greater than 6 items.")
+	}
+}
+
+func TestMinItems(t *testing.T) {
+	// the list to test
+	testList := []core.OptionAnswer{
+		core.OptionAnswer{Value: "a", Index: 0},
+		core.OptionAnswer{Value: "b", Index: 1},
+		core.OptionAnswer{Value: "c", Index: 2},
+		core.OptionAnswer{Value: "d", Index: 3},
+		core.OptionAnswer{Value: "e", Index: 4},
+		core.OptionAnswer{Value: "f", Index: 5},
+	}
+
+	// validate the list
+	if err := MinItems(10)(testList); err == nil {
+		t.Error("No error returned with input less than 10 items.")
+	}
+}
+
 func TestMaxLength(t *testing.T) {
 	// the string to test
 	testStr := randString(150)