Repository: alecthomas/kong
Pull request #310 (2022-06-11 19:47:45)

helpful error message when flag is missing values
Intended to address #277.


Patch:
diff --git a/mapper.go b/mapper.go
index 796a6cc..231ce09 100644
--- a/mapper.go
+++ b/mapper.go
@@ -439,17 +439,17 @@ func mapDecoder(r *Registry) MapperFunc {
 			target.Set(reflect.MakeMap(target.Type()))
 		}
 		el := target.Type()
-		sep := ctx.Value.Tag.MapSep
+		mapsep := ctx.Value.Tag.MapSep
 		var childScanner *Scanner
 		if ctx.Value.Flag != nil {
 			t := ctx.Scan.Pop()
-			// If decoding a flag, we need an argument.
+			// If decoding a flag, we need an value.
 			if t.IsEOL() {
-				return errors.New("unexpected EOL")
+				return fmt.Errorf("missing value, expecting \"<key>=<value>%c...\"", mapsep)
 			}
 			switch v := t.Value.(type) {
 			case string:
-				childScanner = ScanAsType(t.Type, SplitEscaped(v, sep)...)
+				childScanner = ScanAsType(t.Type, SplitEscaped(v, mapsep)...)
 
 			case []map[string]interface{}:
 				for _, m := range v {
@@ -518,9 +518,9 @@ func sliceDecoder(r *Registry) MapperFunc {
 		var childScanner *Scanner
 		if ctx.Value.Flag != nil {
 			t := ctx.Scan.Pop()
-			// If decoding a flag, we need an argument.
+			// If decoding a flag, we need an value.
 			if t.IsEOL() {
-				return errors.New("unexpected EOL")
+				return fmt.Errorf("missing value, expecting \"<arg>%c...\"", sep)
 			}
 			switch v := t.Value.(type) {
 			case string:


Test patch:
diff --git a/kong_test.go b/kong_test.go
index 9848356..7549906 100644
--- a/kong_test.go
+++ b/kong_test.go
@@ -1561,3 +1561,103 @@ func TestHelpShouldStillWork(t *testing.T) {
 	// program; errors will not propagate in the real world).
 	require.Error(t, err)
 }
+
+func TestSliceDecoderHelpfulErrorMsg(t *testing.T) {
+	tests := []struct {
+		name string
+		cli  interface{}
+		args []string
+		err  string
+	}{
+		{
+			"DefaultRune",
+			&struct {
+				Stuff []string
+			}{},
+			[]string{"--stuff"},
+			`--stuff: missing value, expecting "<arg>,..."`,
+		},
+		{
+			"SpecifiedRune",
+			&struct {
+				Stuff []string `sep:","`
+			}{},
+			[]string{"--stuff"},
+			`--stuff: missing value, expecting "<arg>,..."`,
+		},
+		{
+			"SpaceRune",
+			&struct {
+				Stuff []string `sep:" "`
+			}{},
+			[]string{"--stuff"},
+			`--stuff: missing value, expecting "<arg> ..."`,
+		},
+		{
+			"OtherRune",
+			&struct {
+				Stuff []string `sep:"_"`
+			}{},
+			[]string{"--stuff"},
+			`--stuff: missing value, expecting "<arg>_..."`,
+		},
+	}
+	for _, test := range tests {
+		test := test
+		t.Run(test.name, func(t *testing.T) {
+			p := mustNew(t, test.cli)
+			_, err := p.Parse(test.args)
+			require.EqualError(t, err, test.err)
+		})
+	}
+}
+
+func TestMapDecoderHelpfulErrorMsg(t *testing.T) {
+	tests := []struct {
+		name     string
+		cli      interface{}
+		args     []string
+		expected string
+	}{
+		{
+			"DefaultRune",
+			&struct {
+				Stuff map[string]int
+			}{},
+			[]string{"--stuff"},
+			`--stuff: missing value, expecting "<key>=<value>;..."`,
+		},
+		{
+			"SpecifiedRune",
+			&struct {
+				Stuff map[string]int `mapsep:";"`
+			}{},
+			[]string{"--stuff"},
+			`--stuff: missing value, expecting "<key>=<value>;..."`,
+		},
+		{
+			"SpaceRune",
+			&struct {
+				Stuff map[string]int `mapsep:" "`
+			}{},
+			[]string{"--stuff"},
+			`--stuff: missing value, expecting "<key>=<value> ..."`,
+		},
+		{
+			"OtherRune",
+			&struct {
+				Stuff map[string]int `mapsep:","`
+			}{},
+			[]string{"--stuff"},
+			`--stuff: missing value, expecting "<key>=<value>,..."`,
+		},
+	}
+	for _, test := range tests {
+		test := test
+		t.Run(test.name, func(t *testing.T) {
+			p := mustNew(t, test.cli)
+			_, err := p.Parse(test.args)
+			require.EqualError(t, err, test.expected)
+		})
+	}
+}