Skip to content

Commit 7446716

Browse files
authored
Change minLength prompt password check from validator to option (#166)
1 parent 3977fe0 commit 7446716

File tree

6 files changed

+156
-83
lines changed

6 files changed

+156
-83
lines changed

ui/options.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type options struct {
1414
value string
1515
allowEdit bool
1616
printTemplate string
17+
minLength int
1718
promptTemplates *promptui.PromptTemplates
1819
selectTemplates *promptui.SelectTemplates
1920
validateFunc promptui.ValidateFunc
@@ -109,6 +110,15 @@ func WithAllowEdit(b bool) Option {
109110
}
110111
}
111112

113+
// WithMinLength sets a minimum length requirement that will be verified
114+
// at the time when the prompt is run.
115+
// checks the input string meets the minimum length requirement.
116+
func WithMinLength(minLength int) Option {
117+
return func(o *options) {
118+
o.minLength = minLength
119+
}
120+
}
121+
112122
// WithPrintTemplate sets the template to use on the print methods.
113123
func WithPrintTemplate(template string) Option {
114124
return func(o *options) {
@@ -143,12 +153,6 @@ func WithValidateNotEmpty() Option {
143153
return WithValidateFunc(NotEmpty())
144154
}
145155

146-
// WithValidateMinLength adds a custom validation function to a prompt that
147-
// checks the input string meets the minimum length requirement.
148-
func WithValidateMinLength(minLength int) Option {
149-
return WithValidateFunc(MinLength(minLength))
150-
}
151-
152156
// WithValidateYesNo adds a custom validation function to a prompt for a Yes/No
153157
// prompt.
154158
func WithValidateYesNo() Option {

ui/options_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package ui
2+
3+
import "testing"
4+
5+
func TestWithMinLength(t *testing.T) {
6+
tests := []struct {
7+
name string
8+
length int
9+
}{
10+
{
11+
name: "negative",
12+
length: -5,
13+
},
14+
{
15+
name: "zero",
16+
length: 0,
17+
},
18+
{
19+
name: "positive",
20+
length: 11,
21+
},
22+
}
23+
for _, tt := range tests {
24+
t.Run(tt.name, func(t *testing.T) {
25+
o := &options{}
26+
WithMinLength(tt.length)(o)
27+
if o.minLength != tt.length {
28+
t.Errorf("want %v, but got %v", tt.length, o.minLength)
29+
}
30+
})
31+
}
32+
}

ui/ui.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66
"strings"
77
"text/template"
8+
"unicode"
89

910
"github.com/chzyer/readline"
1011
"github.com/manifoldco/promptui"
@@ -203,13 +204,33 @@ func PromptPassword(label string, opts ...Option) ([]byte, error) {
203204
Validate: o.validateFunc,
204205
Templates: o.promptTemplates,
205206
}
206-
pass, err := prompt.Run()
207+
208+
pass, err := runPrompt(prompt.Run, o)
207209
if err != nil {
208-
return nil, errors.Wrap(err, "error reading password")
210+
return nil, err
209211
}
210212
return []byte(pass), nil
211213
}
212214

215+
// runPrompt is a helper for the method prompt.Run. This helper will loop the
216+
// prompt indefinitely while the input does not meet a minimum length requirement.
217+
func runPrompt(run func() (string, error), opts *options) (string, error) {
218+
for {
219+
pass, err := run()
220+
if err != nil {
221+
return "", errors.Wrap(err, "error reading password")
222+
}
223+
224+
pass = strings.TrimRightFunc(pass, unicode.IsSpace)
225+
226+
if opts.minLength <= 0 || len(pass) >= opts.minLength {
227+
return pass, nil
228+
}
229+
230+
fmt.Printf(">>> input does not meet minimum length requirement; must be at least %v characters\n", opts.minLength)
231+
}
232+
}
233+
213234
// PromptPasswordGenerate creates and runs a promptui.Prompt with the given label.
214235
// This prompt will mask the key entries with \r. If the result password length
215236
// is 0, it will generate a new prompt with a generated password that can be

ui/ui_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package ui
2+
3+
import (
4+
"errors"
5+
"testing"
6+
)
7+
8+
func Test_promptRun(t *testing.T) {
9+
promptRunner := func(input []string, err error) func() (string, error) {
10+
i := 0
11+
return func() (string, error) {
12+
ret := input[i]
13+
i++
14+
return ret, err
15+
}
16+
}
17+
18+
tests := []struct {
19+
name string
20+
minLength int
21+
promptRun func() (string, error)
22+
want string
23+
wantErr bool
24+
}{
25+
{
26+
name: "prompt-error",
27+
minLength: -5,
28+
promptRun: promptRunner([]string{"foobar"}, errors.New("prompt-error")),
29+
want: "foobar",
30+
wantErr: true,
31+
},
32+
{
33+
name: "negative",
34+
minLength: -5,
35+
promptRun: promptRunner([]string{"foobar"}, nil),
36+
want: "foobar",
37+
wantErr: false,
38+
},
39+
{
40+
name: "zero",
41+
minLength: 0,
42+
promptRun: promptRunner([]string{"foobar"}, nil),
43+
want: "foobar",
44+
wantErr: false,
45+
},
46+
{
47+
name: "greater-than-min-length",
48+
minLength: 5,
49+
promptRun: promptRunner([]string{"foobar"}, nil),
50+
want: "foobar",
51+
wantErr: false,
52+
},
53+
{
54+
name: "equal-min-length",
55+
minLength: 6,
56+
promptRun: promptRunner([]string{"foobar"}, nil),
57+
want: "foobar",
58+
wantErr: false,
59+
},
60+
{
61+
name: "less-than-min-length",
62+
minLength: 8,
63+
promptRun: promptRunner([]string{"pass", "foobar", "password"}, nil),
64+
want: "password",
65+
wantErr: false,
66+
},
67+
{
68+
name: "ignore-post-whitespace-characters",
69+
minLength: 7,
70+
promptRun: promptRunner([]string{"pass ", "foobar ", "password "}, nil),
71+
want: "password",
72+
wantErr: false,
73+
},
74+
}
75+
for _, tt := range tests {
76+
t.Run(tt.name, func(t *testing.T) {
77+
val, err := runPrompt(tt.promptRun, &options{minLength: tt.minLength})
78+
gotErr := err != nil
79+
if gotErr != tt.wantErr {
80+
t.Errorf("expected error=%v, but got error=%v", tt.wantErr, err)
81+
return
82+
}
83+
if gotErr {
84+
return
85+
}
86+
if val != tt.want {
87+
t.Errorf("expected %v, but got %v", tt.want, val)
88+
}
89+
})
90+
}
91+
}

ui/validators.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"net"
77
"strings"
8-
"unicode"
98

109
"github.com/manifoldco/promptui"
1110
)
@@ -74,17 +73,3 @@ func YesNo() promptui.ValidateFunc {
7473
}
7574
}
7675
}
77-
78-
// MinLength is a validation function that checks for a minimum length.
79-
// An input length <= 0 indicates that the check should not be performed.
80-
func MinLength(minLength int) promptui.ValidateFunc {
81-
return func(s string) error {
82-
if minLength <= 0 {
83-
return nil
84-
}
85-
if len(strings.TrimRightFunc(s, unicode.IsSpace)) < minLength {
86-
return fmt.Errorf("input does not meet minimum length requirement; must be at least %v characters", minLength)
87-
}
88-
return nil
89-
}
90-
}

ui/validators_test.go

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -90,63 +90,3 @@ func TestDNS(t *testing.T) {
9090
})
9191
}
9292
}
93-
94-
func TestMinLength(t *testing.T) {
95-
tests := []struct {
96-
name string
97-
length int
98-
input string
99-
wantErr bool
100-
}{
101-
{
102-
name: "negative",
103-
length: -5,
104-
input: "foobar",
105-
wantErr: false,
106-
},
107-
{
108-
name: "zero",
109-
length: 0,
110-
input: "localhost",
111-
wantErr: false,
112-
},
113-
{
114-
name: "greater-than-min-length",
115-
length: 5,
116-
input: "foobar",
117-
wantErr: false,
118-
},
119-
{
120-
name: "equal-min-length",
121-
length: 6,
122-
input: "foobar",
123-
wantErr: false,
124-
},
125-
{
126-
name: "less-than-min-length",
127-
length: 8,
128-
input: "foobar",
129-
wantErr: true,
130-
},
131-
{
132-
name: "ignore-post-whitespace-characters",
133-
length: 7,
134-
input: " pass ",
135-
wantErr: true,
136-
},
137-
{
138-
name: "ignore-post-whitespace-characters-ok",
139-
length: 6,
140-
input: " pass ",
141-
wantErr: false,
142-
},
143-
}
144-
for _, tt := range tests {
145-
t.Run(tt.name, func(t *testing.T) {
146-
gotErr := MinLength(tt.length)(tt.input) != nil
147-
if gotErr != tt.wantErr {
148-
t.Errorf("MinLength(%v)(%s) = %v, want %v", tt.length, tt.input, gotErr, tt.wantErr)
149-
}
150-
})
151-
}
152-
}

0 commit comments

Comments
 (0)