-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 7192d37
Showing
5 changed files
with
248 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/gostalt/validate | ||
|
||
go 1.12 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package validate | ||
|
||
// Message represents a failed validation. It contains details | ||
// of the param that failed, as well as the error message from | ||
// the rule that caused it to fail. | ||
type Message struct { | ||
Error string `json:"error"` | ||
Param string `json:"param"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package validate | ||
|
||
import ( | ||
"net/http" | ||
) | ||
|
||
// Rule represents a check to run on a request. | ||
type Rule struct { | ||
// Param is the field in the Request to check. | ||
Param string | ||
// Check is a callback that is ran on the request. The | ||
// second argument is the param to check. If the check | ||
// fails, an error should be returned with details why. | ||
Check func(*http.Request, string) error | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package validate | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
) | ||
|
||
// Validator is responsible for collecting an http.Request and | ||
// a list of rules and checking that the rules are satisfied | ||
// by the given request. | ||
type Validator struct { | ||
request *http.Request | ||
Rules []Rule | ||
} | ||
|
||
// Respond is a helper method that writes the errors to the given | ||
// http.ResponseWriter. This also sets an appropriate HTTP header | ||
// and sets the content-type to JSON. | ||
func Respond(w http.ResponseWriter, m []Message) { | ||
w.Header().Set("Content-Type", "application/json") | ||
w.WriteHeader(http.StatusUnprocessableEntity) | ||
|
||
eb := make(map[string][]Message) | ||
eb["errors"] = m | ||
|
||
d, _ := json.Marshal(eb) | ||
w.Write(d) | ||
} | ||
|
||
// Check is an all-in-one method of creating and running a new | ||
// Validator. If there is no logic around adding rules, it is | ||
// the easiest way to run a Validator. | ||
func Check(r *http.Request, rule ...Rule) ([]Message, error) { | ||
return Make(r, rule...).Run() | ||
} | ||
|
||
// Make creates a new Validator based on the request and rules | ||
// passed into it. The rules argument is optional. Rules can | ||
// be added by calling `Add` on the returned Validator. | ||
func Make(r *http.Request, rule ...Rule) *Validator { | ||
return &Validator{ | ||
request: r, | ||
Rules: rule, | ||
} | ||
} | ||
|
||
// Run determines if the given rules are satisfied by the request. | ||
// A "perfect" outcome is `nil, nil`. | ||
func (v *Validator) Run() ([]Message, error) { | ||
if len(v.Rules) == 0 { | ||
return nil, fmt.Errorf("no rules defined on validator") | ||
} | ||
|
||
// The number of messages can't exceed the number of rules, | ||
// so define an upper limit here for speed. | ||
vm := make([]Message, 0, len(v.Rules)) | ||
|
||
for _, rule := range v.Rules { | ||
if err := rule.Check(v.request, rule.Param); err != nil { | ||
vm = append(vm, Message{ | ||
Error: err.Error(), | ||
Param: rule.Param, | ||
}) | ||
} | ||
} | ||
|
||
if len(vm) > 0 { | ||
return vm, errors.New("validation failed") | ||
} | ||
|
||
return nil, nil | ||
} | ||
|
||
// Add adds an additional set of rules to the Validator. | ||
func (v *Validator) Add(rules ...Rule) { | ||
v.Rules = append(v.Rules, rules...) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package validate | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
"testing" | ||
) | ||
|
||
func TestCheckCreatesValidatorAndRunsIt(t *testing.T) { | ||
r, _ := http.NewRequest("GET", "localhost", nil) | ||
|
||
rule := Rule{ | ||
Param: "forename", | ||
Check: func(r *http.Request, param string) error { | ||
return errors.New("fail") | ||
}, | ||
} | ||
|
||
rule2 := Rule{ | ||
Param: "forename", | ||
Check: func(r *http.Request, param string) error { | ||
return errors.New("fail") | ||
}, | ||
} | ||
|
||
msgs, _ := Check(r, rule, rule2) | ||
if len(msgs) == 0 { | ||
fmt.Println("expected an error, didn't get one") | ||
t.FailNow() | ||
} | ||
} | ||
|
||
func TestMakeReturnsErrorWithNoRules(t *testing.T) { | ||
r, _ := http.NewRequest("GET", "localhost", nil) | ||
|
||
validator := Make(r) | ||
|
||
if _, err := validator.Run(); err == nil { | ||
fmt.Println("no error returned from empty validator") | ||
t.FailNow() | ||
} | ||
} | ||
|
||
func TestCanAddRules(t *testing.T) { | ||
r, _ := http.NewRequest("GET", "localhost", nil) | ||
|
||
validator := Make(r) | ||
|
||
rule := Rule{} | ||
|
||
validator.Add(rule) | ||
|
||
if len(validator.Rules) == 0 { | ||
fmt.Println("validator empty, expected 1 rule") | ||
t.FailNow() | ||
} | ||
} | ||
|
||
func TestFailureReturnsValidationMessage(t *testing.T) { | ||
r, _ := http.NewRequest("GET", "localhost", nil) | ||
|
||
validator := Make(r) | ||
|
||
rule := Rule{ | ||
Param: "forename", | ||
Check: func(r *http.Request, param string) error { | ||
return errors.New("fail") | ||
}, | ||
} | ||
|
||
validator.Add(rule) | ||
|
||
messages, _ := validator.Run() | ||
|
||
if len(messages) == 0 { | ||
fmt.Println("expected a validation message, got none") | ||
t.FailNow() | ||
} | ||
} | ||
|
||
func TestValidatorRunsRuleCheck(t *testing.T) { | ||
r, _ := http.NewRequest("GET", "localhost", nil) | ||
|
||
validator := Make(r) | ||
|
||
rule := Rule{ | ||
Param: "forename", | ||
Check: func(r *http.Request, param string) error { | ||
return errors.New("forced failure") | ||
}, | ||
} | ||
|
||
validator.Add(rule) | ||
|
||
if _, err := validator.Run(); err == nil { | ||
fmt.Println("expected validator to fail. It didn't.") | ||
t.FailNow() | ||
} | ||
} | ||
|
||
func TestValidatorReturnsRuleCheckErrorMessage(t *testing.T) { | ||
r, _ := http.NewRequest("GET", "localhost", nil) | ||
|
||
validator := Make(r) | ||
|
||
rule := Rule{ | ||
Param: "forename", | ||
Check: func(r *http.Request, param string) error { | ||
return errors.New("forced failure") | ||
}, | ||
} | ||
|
||
validator.Add(rule) | ||
|
||
messages, _ := validator.Run() | ||
if messages[0].Error != "forced failure" { | ||
fmt.Println("expected the message to contain the rule error. It didn't.") | ||
t.FailNow() | ||
} | ||
} | ||
|
||
func TestValidatorReturnsParamInError(t *testing.T) { | ||
r, _ := http.NewRequest("GET", "localhost", nil) | ||
|
||
validator := Make(r) | ||
|
||
rule := Rule{ | ||
Param: "forename", | ||
Check: func(r *http.Request, param string) error { | ||
return errors.New("forced failure") | ||
}, | ||
} | ||
|
||
validator.Add(rule) | ||
|
||
messages, _ := validator.Run() | ||
if messages[0].Param != "forename" { | ||
fmt.Println("expected the message to contain the param. It didn't.") | ||
t.FailNow() | ||
} | ||
} |