diff --git a/api/characteristics.go b/api/characteristics.go index 2fa1965..11f2d52 100644 --- a/api/characteristics.go +++ b/api/characteristics.go @@ -137,6 +137,9 @@ func (c CharacteristicService) Update(id int64, e *types.Entity, genus string, c if err == errors.ErrCharacteristicNotUpdated { return newJSONError(err, http.StatusBadRequest) } + if err, ok := err.(types.ValidationError); ok { + return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity} + } return newJSONError(err, http.StatusInternalServerError) } @@ -176,6 +179,9 @@ func (c CharacteristicService) Create(e *types.Entity, genus string, claims *typ payload.Characteristic.CharacteristicTypeID = id if err := models.Create(payload.Characteristic.CharacteristicBase); err != nil { + if err, ok := err.(types.ValidationError); ok { + return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity} + } return newJSONError(err, http.StatusInternalServerError) } diff --git a/api/measurements.go b/api/measurements.go index 205eaa9..53303f4 100644 --- a/api/measurements.go +++ b/api/measurements.go @@ -99,6 +99,9 @@ func (m MeasurementService) Update(id int64, e *types.Entity, genus string, clai if err == errors.ErrMeasurementNotUpdated { return newJSONError(err, http.StatusBadRequest) } + if err, ok := err.(types.ValidationError); ok { + return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity} + } return newJSONError(err, http.StatusInternalServerError) } @@ -132,6 +135,9 @@ func (m MeasurementService) Create(e *types.Entity, genus string, claims *types. payload.Measurement.UpdatedBy = claims.Sub if err := models.Create(payload.Measurement.MeasurementBase); err != nil { + if err, ok := err.(types.ValidationError); ok { + return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity} + } return newJSONError(err, http.StatusInternalServerError) } diff --git a/api/species.go b/api/species.go index 9ecd680..6d1516d 100644 --- a/api/species.go +++ b/api/species.go @@ -97,6 +97,9 @@ func (s SpeciesService) Update(id int64, e *types.Entity, genus string, claims * if err == errors.ErrSpeciesNotUpdated { return newJSONError(err, http.StatusBadRequest) } + if err, ok := err.(types.ValidationError); ok { + return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity} + } return newJSONError(err, http.StatusInternalServerError) } @@ -133,6 +136,9 @@ func (s SpeciesService) Create(e *types.Entity, genus string, claims *types.Clai payload.Species.SpeciesBase.GenusID = genusID if err := models.Create(payload.Species.SpeciesBase); err != nil { + if err, ok := err.(types.ValidationError); ok { + return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity} + } return newJSONError(err, http.StatusInternalServerError) } diff --git a/api/strains.go b/api/strains.go index 4cdbad8..4789eaf 100644 --- a/api/strains.go +++ b/api/strains.go @@ -159,6 +159,9 @@ func (s StrainService) Update(id int64, e *types.Entity, genus string, claims *t if err == errors.ErrStrainNotUpdated { return newJSONError(err, http.StatusBadRequest) } + if err, ok := err.(types.ValidationError); ok { + return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity} + } return newJSONError(err, http.StatusInternalServerError) } @@ -190,6 +193,9 @@ func (s StrainService) Create(e *types.Entity, genus string, claims *types.Claim payload.Strain.UpdatedBy = claims.Sub if err := models.Create(payload.Strain.StrainBase); err != nil { + if err, ok := err.(types.ValidationError); ok { + return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity} + } return newJSONError(err, http.StatusInternalServerError) } diff --git a/api/users.go b/api/users.go index a2591d6..c79c7db 100644 --- a/api/users.go +++ b/api/users.go @@ -105,14 +105,13 @@ func (u UserService) Update(id int64, e *types.Entity, dummy string, claims *typ user.Verified = originalUser.Verified user.UpdatedAt = helpers.CurrentTime() - if err := user.Validate(); err != nil { - return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity} - } - if err := models.Update(user.UserBase); err != nil { if err == errors.ErrUserNotUpdated { return newJSONError(err, http.StatusBadRequest) } + if err, ok := err.(types.ValidationError); ok { + return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity} + } return newJSONError(err, http.StatusInternalServerError) } @@ -124,9 +123,7 @@ func (u UserService) Update(id int64, e *types.Entity, dummy string, claims *typ // Create initializes a new user. func (u UserService) Create(e *types.Entity, dummy string, claims *types.Claims) *types.AppError { user := (*e).(*payloads.User).User - if err := user.Validate(); err != nil { - return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity} - } + ct := helpers.CurrentTime() user.CreatedAt = ct user.UpdatedAt = ct @@ -144,6 +141,9 @@ func (u UserService) Create(e *types.Entity, dummy string, claims *types.Claims) return newJSONError(errors.ErrEmailAddressTaken, http.StatusInternalServerError) } } + if err, ok := err.(types.ValidationError); ok { + return &types.AppError{Error: err, Status: helpers.StatusUnprocessableEntity} + } return newJSONError(err, http.StatusInternalServerError) } diff --git a/models/characteristics.go b/models/characteristics.go index 3be62b6..8d73f84 100644 --- a/models/characteristics.go +++ b/models/characteristics.go @@ -38,6 +38,24 @@ func (c *CharacteristicBase) DeleteError() error { return errors.ErrCharacteristicNotDeleted } +func (c *CharacteristicBase) validate() types.ValidationError { + cv := make(types.ValidationError, 0) + + if c.CharacteristicName == "" { + cv["Name"] = []string{helpers.MustProvideAValue} + } + + if c.CharacteristicTypeID == 0 { + cv["Characteristic Type"] = []string{helpers.MustProvideAValue} + } + + if len(cv) > 0 { + return cv + } + + return nil +} + // CharacteristicBase is what the DB expects for write operations type CharacteristicBase struct { ID int64 `json:"id,omitempty"` diff --git a/models/interfaces.go b/models/interfaces.go index c0e010b..725dc63 100644 --- a/models/interfaces.go +++ b/models/interfaces.go @@ -1,16 +1,24 @@ package models -import "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" +import ( + "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" + "github.com/thermokarst/bactdb/types" +) type base interface { PreInsert(modl.SqlExecutor) error PreUpdate(modl.SqlExecutor) error UpdateError() error DeleteError() error + validate() types.ValidationError } // Create will create a new DB record of a model. func Create(b base) error { + if err := b.validate(); err != nil { + return err + } + if err := DBH.Insert(b); err != nil { return nil } @@ -19,6 +27,10 @@ func Create(b base) error { // Update runs a DB update on a model. func Update(b base) error { + if err := b.validate(); err != nil { + return err + } + count, err := DBH.Update(b) if err != nil { return err @@ -26,6 +38,7 @@ func Update(b base) error { if count != 1 { return b.UpdateError() } + return nil } @@ -38,5 +51,6 @@ func Delete(b base) error { if count != 1 { return b.DeleteError() } + return nil } diff --git a/models/measurements.go b/models/measurements.go index 4fdf733..e234b2c 100644 --- a/models/measurements.go +++ b/models/measurements.go @@ -39,6 +39,24 @@ func (m *MeasurementBase) DeleteError() error { return errors.ErrMeasurementNotDeleted } +func (m *MeasurementBase) validate() types.ValidationError { + mv := make(types.ValidationError, 0) + + if m.StrainID == 0 { + mv["Strain"] = []string{helpers.MustProvideAValue} + } + + if m.CharacteristicID == 0 { + mv["Characteristic"] = []string{helpers.MustProvideAValue} + } + + if len(mv) > 0 { + return mv + } + + return nil +} + // MeasurementBase is what the DB expects for write operations // There are three types of supported measurements: fixed-text, free-text, // & numerical. The table has a constraint that will allow at most one diff --git a/models/species.go b/models/species.go index 5bec47c..62a5e8a 100644 --- a/models/species.go +++ b/models/species.go @@ -39,6 +39,24 @@ func (s *SpeciesBase) DeleteError() error { return errors.ErrSpeciesNotDeleted } +func (s *SpeciesBase) validate() types.ValidationError { + sv := make(types.ValidationError, 0) + + if s.GenusID == 0 { + sv["Genus"] = []string{helpers.MustProvideAValue} + } + + if s.SpeciesName == "" { + sv["Species"] = []string{helpers.MustProvideAValue} + } + + if len(sv) > 0 { + return sv + } + + return nil +} + // SpeciesBase is what the DB expects for write operations. type SpeciesBase struct { ID int64 `db:"id" json:"id"` diff --git a/models/strains.go b/models/strains.go index a2389aa..6420878 100644 --- a/models/strains.go +++ b/models/strains.go @@ -39,6 +39,24 @@ func (s *StrainBase) DeleteError() error { return errors.ErrStrainNotDeleted } +func (s *StrainBase) validate() types.ValidationError { + sv := make(types.ValidationError, 0) + + if s.SpeciesID == 0 { + sv["Species"] = []string{helpers.MustProvideAValue} + } + + if s.StrainName == "" { + sv["Name"] = []string{helpers.MustProvideAValue} + } + + if len(sv) > 0 { + return sv + } + + return nil +} + // StrainBase is what the DB expects for write operations. type StrainBase struct { ID int64 `db:"id" json:"id"` diff --git a/models/users.go b/models/users.go index d9cf9bb..5f18d45 100644 --- a/models/users.go +++ b/models/users.go @@ -2,7 +2,6 @@ package models import ( "database/sql" - "encoding/json" "regexp" "github.com/thermokarst/bactdb/Godeps/_workspace/src/github.com/jmoiron/modl" @@ -40,6 +39,33 @@ func (u *UserBase) DeleteError() error { return errors.ErrUserNotDeleted } +func (u *UserBase) validate() types.ValidationError { + uv := make(types.ValidationError, 0) + + if u.Name == "" { + uv["Name"] = []string{helpers.MustProvideAValue} + } + + if u.Email == "" { + uv["Email"] = []string{helpers.MustProvideAValue} + } + + regex, _ := regexp.Compile(`(\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3})`) + if u.Email != "" && !regex.MatchString(u.Email) { + uv["Email"] = []string{"Must provide a valid email address"} + } + + if len(u.Password) < 8 { + uv["Password"] = []string{"Password must be at least 8 characters"} + } + + if len(uv) > 0 { + return uv + } + + return nil +} + // UserBase is what the DB expects to see for write operations. type UserBase struct { ID int64 `json:"id,omitempty"` @@ -68,17 +94,6 @@ type UserValidation struct { Role []string `json:"role,omitempty"` } -// Error returns the JSON-encoded error response for any validation errors. -func (uv UserValidation) Error() string { - errs, err := json.Marshal(struct { - UserValidation `json:"errors"` - }{uv}) - if err != nil { - return err.Error() - } - return string(errs) -} - // Users are multiple user entities. type Users []*User @@ -87,38 +102,6 @@ type UserMeta struct { CanAdd bool `json:"canAdd"` } -// Validate validates a user record. -func (u *User) Validate() error { - var uv UserValidation - validationError := false - - if u.Name == "" { - uv.Name = append(uv.Name, helpers.MustProvideAValue) - validationError = true - } - - if u.Email == "" { - uv.Email = append(uv.Email, helpers.MustProvideAValue) - validationError = true - } - - regex, _ := regexp.Compile(`(\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3})`) - if u.Email != "" && !regex.MatchString(u.Email) { - uv.Email = append(uv.Email, "Must provide a valid email address") - validationError = true - } - - if len(u.Password) < 8 { - uv.Password = append(uv.Password, "Password must be at least 8 characters") - validationError = true - } - - if validationError { - return uv - } - return nil -} - // DbAuthenticate authenticates a user. // For thermokarst/jwt: authentication callback func DbAuthenticate(email string, password string) error { diff --git a/types/validation-error.go b/types/validation-error.go new file mode 100644 index 0000000..8ffe2d7 --- /dev/null +++ b/types/validation-error.go @@ -0,0 +1,17 @@ +package types + +import "encoding/json" + +type ValidationError map[string][]string + +func (v ValidationError) Error() string { + errs, err := json.Marshal(struct { + ValidationError `json:"errors"` + }{v}) + + if err != nil { + return err.Error() + } + + return string(errs) +}