Skip to content

Commit

Permalink
refactors validation errors
Browse files Browse the repository at this point in the history
  • Loading branch information
casualjim committed Feb 28, 2015
1 parent 6ca7958 commit 6fbcb10
Show file tree
Hide file tree
Showing 32 changed files with 469 additions and 300 deletions.
6 changes: 5 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ func (r *Runtime) Submit(request *Request, result interface{}) error {
return err
}
} else {
return &APIError{OperationName: request.Operation.ID, Value: fmt.Sprintf("no consumer for %q", consumerMediaType), Code: res.StatusCode}
return &APIError{
OperationName: request.Operation.ID,
Value: fmt.Sprintf("no consumer for %q", consumerMediaType),
Code: res.StatusCode,
}
}
}
return nil
Expand Down
7 changes: 4 additions & 3 deletions cmd/swagger/commands/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import (
"errors"
"fmt"

"github.com/casualjim/go-swagger/internal/validate"
swaggererrors "github.com/casualjim/go-swagger/errors"
"github.com/casualjim/go-swagger/spec"
"github.com/casualjim/go-swagger/strfmt"
"github.com/casualjim/go-swagger/validate"
)

// ValidateSpec is a command that validates a swagger document
Expand All @@ -28,11 +29,11 @@ func (c *ValidateSpec) Execute(args []string) error {
}

result := validate.Spec(specDoc, strfmt.Default)
if result.IsValid() {
if result == nil {
fmt.Printf("The swagger spec at %q is valid against swagger specification %s\n", swaggerDoc, specDoc.Version())
} else {
str := fmt.Sprintf("The swagger spec at %q is invalid against swagger specification %s. see errors :\n", swaggerDoc, specDoc.Version())
for _, desc := range result.Errors {
for _, desc := range result.(*swaggererrors.CompositeError).Errors {
str += fmt.Sprintf("- %s\n", desc)
}
return errors.New(str)
Expand Down
4 changes: 2 additions & 2 deletions design.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ When a result is produced it will do the same thing by making use of the `Accept

```go
type RequestBinder interface {
BindRequest(*http.Request, *router.MatchedRoute, swagger.Consumer) *validate.Result
BindRequest(*http.Request, *router.MatchedRoute, swagger.Consumer) error
}
```

Expand All @@ -115,7 +115,7 @@ There is a mapping from validation name to status code, this mapping is also pri

```go
type Validatable interface {
Validate() *validate.Result
Validate(strfmt.Registry) error
}

type Error struct {
Expand Down
21 changes: 21 additions & 0 deletions devtools.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/sh

go get -u github.com/golang/lint/golint
go get -u golang.org/x/tools/cmd/...
go get -u github.com/tools/godep
go get -u github.com/jteeuwen/go-bindata/...
go get -u github.com/elazarl/go-bindata-assetfs/...
go get -u github.com/redefiance/go-find-references
go get -u github.com/sqs/goreturns
go get -u code.google.com/p/gomock/gomock
go get -u code.google.com/p/gomock/mockgen
go get -u github.com/axw/gocov/gocov
go get -u gopkg.in/matm/v1/gocov-html
go get -u github.com/AlekSi/gocov-xml
go get -u github.com/nsf/gocode
go get -u github.com/kisielk/errcheck
go get -u github.com/jstemmer/gotags
go get -u github.com/smartystreets/goconvey
go get -u github.com/rogpeppe/godef
go get -u github.com/pquerna/ffjson
go get -u github.com/clipperhouse/gen
34 changes: 28 additions & 6 deletions errors/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@ func errorAsJSON(err Error) []byte {
return b
}

func flattenComposite(errs *CompositeError) *CompositeError {
var res []error
for _, er := range errs.Errors {
switch e := er.(type) {
case *CompositeError:
flat := flattenComposite(e)
res = append(res, flat.Errors...)
default:
res = append(res, e)
}
}
return CompositeValidationError(res...)
}

// MethodNotAllowed creates a new method not allowed error
func MethodNotAllowed(requested string, allow []string) Error {
msg := fmt.Sprintf("method %s is not allowed, but [%s] are", requested, strings.Join(allow, ","))
Expand All @@ -79,18 +93,26 @@ func MethodNotAllowed(requested string, allow []string) Error {

// ServeError the error handler interface implemenation
func ServeError(rw http.ResponseWriter, r *http.Request, err error) {
switch err.(type) {
switch e := err.(type) {
case *CompositeError:
er := flattenComposite(e)
ServeError(rw, r, er.Errors[0])
case *MethodNotAllowedError:
e := err.(*MethodNotAllowedError)
rw.Header().Add("Allow", strings.Join(err.(*MethodNotAllowedError).Allowed, ","))
rw.WriteHeader(int(e.Code()))
rw.Write(errorAsJSON(e))
if r == nil || r.Method != "HEAD" {
rw.Write(errorAsJSON(e))
}
case Error:
rw.WriteHeader(int(err.(Error).Code()))
rw.Write(errorAsJSON(err.(Error)))
rw.WriteHeader(int(e.Code()))
if r == nil || r.Method != "HEAD" {
rw.Write(errorAsJSON(e))
}
default:
rw.WriteHeader(http.StatusInternalServerError)
rw.Write(errorAsJSON(New(http.StatusInternalServerError, err.Error())))
if r == nil || r.Method != "HEAD" {
rw.Write(errorAsJSON(New(http.StatusInternalServerError, err.Error())))
}
}

}
2 changes: 1 addition & 1 deletion errors/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"net/http"
)

// Validation represents a failure of a precondidition
// Validation represents a failure of a precondition
type Validation struct {
code int32
Name string
Expand Down
171 changes: 132 additions & 39 deletions errors/schema.go
Original file line number Diff line number Diff line change
@@ -1,54 +1,147 @@
package errors

import "fmt"
import (
"fmt"
"strings"
)

const (
invalidType = `%s is an invalid type name`
typeFail = `%s in %s must be of type %s`
typeFailWithData = `%s in %s must be of type %s: %q`
typeFailWithError = `%s in %s must be of type %s, because: %s`
requiredFail = `%s in %s is required`
tooLongMessage = `%s in %s should be at most %d chars long`
tooShortMessage = `%s in %s should be at least %d chars long`
patternFail = `%s in %s should match '%s'`
enumFail = `%s in %s should be one of %v`
mulitpleOfFail = `%s in %s should be a multiple of %v`
maxIncFail = `%s in %s should be less than or equal to %v`
maxExcFail = `%s in %s should be less than %v`
minIncFail = `%s in %s should be greater than or equal to %v`
minExcFail = `%s in %s should be greater than %v`
uniqueFail = `%s in %s shouldn't contain duplicates`
maxItemsFail = `%s in %s should have at most %d items`
minItemsFail = `%s in %s should have at least %d items`
typeFailNoIn = `%s must be of type %s`
typeFailWithDataNoIn = `%s must be of type %s: %q`
typeFailWithErrorNoIn = `%s must be of type %s, because: %s`
requiredFailNoIn = `%s is required`
tooLongMessageNoIn = `%s should be at most %d chars long`
tooShortMessageNoIn = `%s should be at least %d chars long`
patternFailNoIn = `%s should match '%s'`
enumFailNoIn = `%s should be one of %v`
mulitpleOfFailNoIn = `%s should be a multiple of %v`
maxIncFailNoIn = `%s should be less than or equal to %v`
maxExcFailNoIn = `%s should be less than %v`
minIncFailNoIn = `%s should be greater than or equal to %v`
minExcFailNoIn = `%s should be greater than %v`
uniqueFailNoIn = `%s shouldn't contain duplicates`
maxItemsFailNoIn = `%s should have at most %d items`
minItemsFailNoIn = `%s should have at least %d items`
noAdditionalItems = "%s in %s can't have additional items"
noAdditionalItemsNoIn = "%s can't have additional items"
invalidType = "%s is an invalid type name"
typeFail = "%s in %s must be of type %s"
typeFailWithData = "%s in %s must be of type %s: %q"
typeFailWithError = "%s in %s must be of type %s, because: %s"
requiredFail = "%s in %s is required"
tooLongMessage = "%s in %s should be at most %d chars long"
tooShortMessage = "%s in %s should be at least %d chars long"
patternFail = "%s in %s should match '%s'"
enumFail = "%s in %s should be one of %v"
mulitpleOfFail = "%s in %s should be a multiple of %v"
maxIncFail = "%s in %s should be less than or equal to %v"
maxExcFail = "%s in %s should be less than %v"
minIncFail = "%s in %s should be greater than or equal to %v"
minExcFail = "%s in %s should be greater than %v"
uniqueFail = "%s in %s shouldn't contain duplicates"
maxItemsFail = "%s in %s should have at most %d items"
minItemsFail = "%s in %s should have at least %d items"
typeFailNoIn = "%s must be of type %s"
typeFailWithDataNoIn = "%s must be of type %s: %q"
typeFailWithErrorNoIn = "%s must be of type %s, because: %s"
requiredFailNoIn = "%s is required"
tooLongMessageNoIn = "%s should be at most %d chars long"
tooShortMessageNoIn = "%s should be at least %d chars long"
patternFailNoIn = "%s should match '%s'"
enumFailNoIn = "%s should be one of %v"
mulitpleOfFailNoIn = "%s should be a multiple of %v"
maxIncFailNoIn = "%s should be less than or equal to %v"
maxExcFailNoIn = "%s should be less than %v"
minIncFailNoIn = "%s should be greater than or equal to %v"
minExcFailNoIn = "%s should be greater than %v"
uniqueFailNoIn = "%s shouldn't contain duplicates"
maxItemsFailNoIn = "%s should have at most %d items"
minItemsFailNoIn = "%s should have at least %d items"
noAdditionalItems = "%s in %s can't have additional items"
noAdditionalItemsNoIn = "%s can't have additional items"
tooFewProperties = "%s in %s should have at least %d properties"
tooFewPropertiesNoIn = "%s should have at least %d properties"
tooManyProperties = "%s in %s should have at most %d properties"
tooManyPropertiesNoIn = "%s should have at most %d properties"
unallowedProperty = "%s.%s in %s is a forbidden property"
unallowedPropertyNoIn = "%s.%s is a forbidden property"
failedAllPatternProps = "%s.%s in %s failed all pattern properties"
failedAllPatternPropsNoIn = "%s.%s failed all pattern properties"
)

// CompositeError is an error that groups several errors together
type CompositeError struct {
Errors []error
code int32
message string
}

func (c *CompositeError) Code() int32 {
return c.code
}

func (c *CompositeError) Error() string {
if len(c.Errors) > 0 {
msgs := []string{c.message + ":"}
for _, e := range c.Errors {
msgs = append(msgs, e.Error())
}
return strings.Join(msgs, "\n")
}
return c.message
}

// CompositeValidationError an error to wrap a bunch of other errors
func CompositeValidationError(errors ...Error) *Validation {
return &Validation{
func CompositeValidationError(errors ...error) *CompositeError {
return &CompositeError{
code: 422,
Value: append([]Error{}, errors...),
Errors: append([]error{}, errors...),
message: "validation failure list",
}
}

// FailedAllPatternProperties an error for when the property doesn't match a pattern
func FailedAllPatternProperties(name, in, key string) *Validation {
msg := fmt.Sprintf(failedAllPatternProps, name, key, in)
if in == "" {
msg = fmt.Sprintf(failedAllPatternPropsNoIn, name, key)
}
return &Validation{
code: 422,
Name: name,
In: in,
Value: key,
message: msg,
}
}

// PropertyNotAllowed an error for when the property doesn't match a pattern
func PropertyNotAllowed(name, in, key string) *Validation {
msg := fmt.Sprintf(unallowedProperty, name, key, in)
if in == "" {
msg = fmt.Sprintf(unallowedPropertyNoIn, name, key)
}
return &Validation{
code: 422,
Name: name,
In: in,
Value: key,
message: msg,
}
}

// TooFewProperties an error for an object with too few properties
func TooFewProperties(name, in string, n int64) *Validation {
msg := fmt.Sprintf(tooFewProperties, name, in, n)
if in == "" {
msg = fmt.Sprintf(tooFewPropertiesNoIn, name, n)
}
return &Validation{
code: 422,
Name: name,
In: in,
Value: n,
message: msg,
}
}

// TooManyProperties an error for an object with too many properties
func TooManyProperties(name, in string, n int64) *Validation {
msg := fmt.Sprintf(tooManyProperties, name, in, n)
if in == "" {
msg = fmt.Sprintf(tooManyPropertiesNoIn, name, n)
}
return &Validation{
code: 422,
Name: name,
In: in,
Value: n,
message: msg,
}
}

// AdditionalItemsNotAllowed an error for invalid additional items
func AdditionalItemsNotAllowed(name, in string) *Validation {
msg := fmt.Sprintf(noAdditionalItems, name, in)
Expand Down
8 changes: 4 additions & 4 deletions errors/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ func TestSchemaErrors(t *testing.T) {
assert.Equal(t, 422, err.Code())
assert.Equal(t, "the collection format \"yada\" is not supported for the query param \"something\"", err.Error())

err = CompositeValidationError()
assert.Error(t, err)
assert.Equal(t, 422, err.Code())
assert.Equal(t, "validation failure list", err.Error())
err2 := CompositeValidationError()
assert.Error(t, err2)
assert.Equal(t, 422, err2.Code())
assert.Equal(t, "validation failure list", err2.Error())
}
Loading

0 comments on commit 6fbcb10

Please sign in to comment.