nicer validation errors (#180)

This commit is contained in:
Victor Vrantchan
2016-09-16 11:23:48 -04:00
committed by GitHub
parent 5ea9115a95
commit 1de9f6bd89
3 changed files with 55 additions and 32 deletions
+4 -4
View File
@@ -128,7 +128,7 @@ func TestCreateUser(t *testing.T) {
{
Username: stringPtr("admin1"),
Password: stringPtr("foobar"),
wantErr: invalidArgumentError{field: "email", required: true},
wantErr: invalidArgumentError{invalidArgument{name: "email", reason: "missing required argument"}},
},
{
Username: stringPtr("admin1"),
@@ -151,7 +151,7 @@ func TestCreateUser(t *testing.T) {
Email: stringPtr("admin1@example.com"),
NeedsPasswordReset: boolPtr(true),
Admin: boolPtr(false),
wantErr: invalidArgumentError{field: "username", required: true},
wantErr: invalidArgumentError{invalidArgument{name: "username", reason: "'@' character not allowed in usernames"}},
},
}
@@ -204,11 +204,11 @@ func TestChangeUserPassword(t *testing.T) {
},
{ // missing token
newPassword: "123cat!",
wantErr: invalidArgumentError{field: "token", required: true},
wantErr: invalidArgumentError{invalidArgument{name: "token", reason: "cannot be empty field"}},
},
{ // missing password
token: "abcd",
wantErr: invalidArgumentError{field: "password", required: true},
wantErr: invalidArgumentError{invalidArgument{name: "new_password", reason: "cannot be empty field"}},
},
}
+18 -15
View File
@@ -3,7 +3,6 @@ package server
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
@@ -17,20 +16,6 @@ var (
errBadRoute = errors.New("bad route")
)
type invalidArgumentError struct {
field string
required bool
}
// invalidArgumentError is returned when one or more arguments are invalid.
func (e invalidArgumentError) Error() string {
req := "optional"
if e.required {
req = "required"
}
return fmt.Sprintf("%s argument invalid or missing: %s", req, e.field)
}
func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
if e, ok := response.(errorer); ok && e.error() != nil {
encodeError(ctx, e.error(), w)
@@ -72,6 +57,24 @@ func encodeError(_ context.Context, err error, w http.ResponseWriter) {
}
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
// decide on error type and encode proper JSON format
type validator interface {
Invalid() []map[string]string
}
if e, ok := err.(validator); ok {
var ve = struct {
Message string `json:"message"`
Errors []map[string]string `json:"errors"`
}{
Message: "Validation Failed",
Errors: e.Invalid(),
}
enc.Encode(ve)
return
}
// other errors
enc.Encode(map[string]interface{}{
"error": err.Error(),
})
+33 -13
View File
@@ -12,36 +12,56 @@ type validationMiddleware struct {
}
func (mw validationMiddleware) NewUser(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
var invalid []invalidArgument
if p.Username == nil {
return nil, invalidArgumentError{field: "username", required: true}
invalid = append(invalid, invalidArgument{name: "username", reason: "missing required argument"})
}
if p.Username != nil {
if strings.Contains(*p.Username, "@") {
// TODO @groob this makes it obvious that the
// validation error needs a "reason" field
return nil, invalidArgumentError{field: "username", required: true}
invalid = append(invalid, invalidArgument{name: "username", reason: "'@' character not allowed in usernames"})
}
}
if p.Password == nil {
return nil, invalidArgumentError{field: "password", required: true}
invalid = append(invalid, invalidArgument{name: "password", reason: "missing required argument"})
}
if p.Email == nil {
return nil, invalidArgumentError{field: "email", required: true}
invalid = append(invalid, invalidArgument{name: "email", reason: "missing required argument"})
}
if len(invalid) != 0 {
return nil, invalidArgumentError(invalid)
}
return mw.Service.NewUser(ctx, p)
}
func (mw validationMiddleware) ResetPassword(ctx context.Context, token, password string) error {
var invalid []invalidArgument
if token == "" {
return invalidArgumentError{field: "token", required: true}
invalid = append(invalid, invalidArgument{name: "token", reason: "cannot be empty field"})
}
if password == "" {
return invalidArgumentError{field: "password", required: true}
invalid = append(invalid, invalidArgument{name: "new_password", reason: "cannot be empty field"})
}
if len(invalid) != 0 {
return invalidArgumentError(invalid)
}
return mw.Service.ResetPassword(ctx, token, password)
}
type invalidArgumentError []invalidArgument
type invalidArgument struct {
name string
reason string
}
// invalidArgumentError is returned when one or more arguments are invalid.
func (e invalidArgumentError) Error() string {
return "validation failed"
}
func (e invalidArgumentError) Invalid() []map[string]string {
var invalid []map[string]string
for _, i := range e {
invalid = append(invalid, map[string]string{"name": i.name, "reason": i.reason})
}
return invalid
}