You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by da...@apache.org on 2018/07/04 02:39:37 UTC

[trafficcontrol] 12/15: convert Validate functions to return a single error with all validation issues

This is an automated email from the ASF dual-hosted git repository.

dangogh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit 8fe0a128446c5303c2dba1f8787c4160ff3cb4b3
Author: Dylan Volz <Dy...@comcast.com>
AuthorDate: Mon Jul 2 07:13:18 2018 -0600

    convert Validate functions to return a single error with all validation issues
---
 lib/go-tc/deliveryservices.go                           | 12 ++++++------
 lib/go-tc/parameters.go                                 | 12 ++++++------
 traffic_ops/traffic_ops_golang/api/api.go               |  6 +++---
 traffic_ops/traffic_ops_golang/api/shared_handlers.go   | 16 ++++++++--------
 .../traffic_ops_golang/api/shared_handlers_test.go      |  2 +-
 traffic_ops/traffic_ops_golang/api/shared_interfaces.go |  2 +-
 traffic_ops/traffic_ops_golang/asn/asns.go              |  5 +++--
 .../traffic_ops_golang/cachegroup/cachegroups.go        |  5 +++--
 traffic_ops/traffic_ops_golang/cdn/cdns.go              |  5 +++--
 .../traffic_ops_golang/coordinate/coordinates.go        |  5 +++--
 .../deliveryservice/deliveryservicesv12.go              |  2 +-
 .../deliveryservice/deliveryservicesv13.go              | 10 +++++-----
 .../deliveryservice/request/comment/comments.go         |  5 +++--
 .../deliveryservice/request/requests.go                 |  4 ++--
 .../deliveryservice/request/validate.go                 |  9 +++++----
 .../deliveryservice/servers/servers.go                  |  5 +++--
 traffic_ops/traffic_ops_golang/division/divisions.go    |  5 +++--
 traffic_ops/traffic_ops_golang/origin/origins.go        |  5 +++--
 traffic_ops/traffic_ops_golang/parameter/parameters.go  |  5 +++--
 .../traffic_ops_golang/physlocation/phys_locations.go   |  5 +++--
 traffic_ops/traffic_ops_golang/profile/profiles.go      |  5 +++--
 .../profileparameter/profile_parameters.go              |  5 +++--
 traffic_ops/traffic_ops_golang/region/regions.go        |  7 +++----
 traffic_ops/traffic_ops_golang/role/roles.go            |  7 ++++---
 traffic_ops/traffic_ops_golang/server/servers.go        | 17 ++++++++---------
 traffic_ops/traffic_ops_golang/status/statuses.go       |  5 +++--
 traffic_ops/traffic_ops_golang/tenant/tenancy.go        |  4 ++--
 traffic_ops/traffic_ops_golang/types/types.go           |  5 +++--
 28 files changed, 97 insertions(+), 83 deletions(-)

diff --git a/lib/go-tc/deliveryservices.go b/lib/go-tc/deliveryservices.go
index ae34729..50983e8 100644
--- a/lib/go-tc/deliveryservices.go
+++ b/lib/go-tc/deliveryservices.go
@@ -379,7 +379,7 @@ func ParseOrgServerFQDN(orgServerFQDN string) (*string, *string, *string, error)
 	return &protocol, &FQDN, port, nil
 }
 
-func (ds *DeliveryServiceNullableV12) Validate(tx *sql.Tx) []error {
+func (ds *DeliveryServiceNullableV12) Validate(tx *sql.Tx) error {
 	ds.Sanitize()
 	isDNSName := validation.NewStringRule(govalidator.IsDNSName, "must be a valid hostname")
 	noPeriods := validation.NewStringRule(tovalidate.NoPeriods, "cannot contain periods")
@@ -402,7 +402,7 @@ func (ds *DeliveryServiceNullableV12) Validate(tx *sql.Tx) []error {
 		toErrs = append(toErrs, errors.New("type fields: "+err.Error()))
 	}
 	if len(toErrs) > 0 {
-		return toErrs
+		return util.JoinErrs(toErrs)
 	}
 	return nil
 }
@@ -423,20 +423,20 @@ func (ds *DeliveryServiceNullableV13) Sanitize() {
 	*ds.DeepCachingType = DeepCachingTypeFromString(string(*ds.DeepCachingType))
 }
 
-func (ds *DeliveryServiceNullableV13) Validate(tx *sql.Tx) []error {
+func (ds *DeliveryServiceNullableV13) Validate(tx *sql.Tx) error {
 	ds.Sanitize()
 	neverOrAlways := validation.NewStringRule(tovalidate.IsOneOfStringICase("NEVER", "ALWAYS"),
 		"must be one of 'NEVER' or 'ALWAYS'")
 	errs := tovalidate.ToErrors(validation.Errors{
 		"deepCachingType": validation.Validate(ds.DeepCachingType, neverOrAlways),
 	})
-	if v12Errs := ds.DeliveryServiceNullableV12.Validate(tx); len(v12Errs) > 0 {
-		errs = append(errs, v12Errs...)
+	if v12Err := ds.DeliveryServiceNullableV12.Validate(tx); v12Err != nil {
+		errs = append(errs, v12Err)
 	}
 	if len(errs) == 0 {
 		return nil
 	}
-	return errs // don't add context, so versions chain well
+	return util.JoinErrs(errs) // don't add context, so versions chain well
 }
 
 // Value implements the driver.Valuer interface
diff --git a/lib/go-tc/parameters.go b/lib/go-tc/parameters.go
index 1151f30..afa6103 100644
--- a/lib/go-tc/parameters.go
+++ b/lib/go-tc/parameters.go
@@ -121,7 +121,7 @@ func (pp *ProfileParametersByNamePost) UnmarshalJSON(bts []byte) error {
 	return nil
 }
 
-func (pp *ProfileParametersByNamePost) Validate(tx *sql.Tx) []error {
+func (pp *ProfileParametersByNamePost) Validate(tx *sql.Tx) error {
 	errs := []error{}
 	ppArr := ([]ProfileParameterByNamePost)(*pp)
 	for i, profileParam := range ppArr {
@@ -132,7 +132,7 @@ func (pp *ProfileParametersByNamePost) Validate(tx *sql.Tx) []error {
 		}
 	}
 	if len(errs) > 0 {
-		return errs
+		return util.JoinErrs(errs)
 	}
 	return nil
 }
@@ -160,7 +160,7 @@ func (pp *PostProfileParam) Sanitize(tx *sql.Tx) {
 	}
 }
 
-func (pp *PostProfileParam) Validate(tx *sql.Tx) []error {
+func (pp *PostProfileParam) Validate(tx *sql.Tx) error {
 	pp.Sanitize(tx)
 	errs := []error{}
 	if pp.ProfileID == nil {
@@ -178,7 +178,7 @@ func (pp *PostProfileParam) Validate(tx *sql.Tx) []error {
 		errs = append(errs, errors.New(fmt.Sprintf("parameters with IDs %v don't all exist", *pp.ParamIDs)))
 	}
 	if len(errs) > 0 {
-		return errs
+		return util.JoinErrs(errs)
 	}
 	return nil
 }
@@ -195,7 +195,7 @@ func (pp *PostParamProfile) Sanitize(tx *sql.Tx) {
 	}
 }
 
-func (pp *PostParamProfile) Validate(tx *sql.Tx) []error {
+func (pp *PostParamProfile) Validate(tx *sql.Tx) error {
 	pp.Sanitize(tx)
 
 	errs := []error{}
@@ -214,7 +214,7 @@ func (pp *PostParamProfile) Validate(tx *sql.Tx) []error {
 		errs = append(errs, errors.New(fmt.Sprintf("profiles with IDs %v don't all exist", *pp.ProfileIDs)))
 	}
 	if len(errs) > 0 {
-		return errs
+		return util.JoinErrs(errs)
 	}
 	return nil
 }
diff --git a/traffic_ops/traffic_ops_golang/api/api.go b/traffic_ops/traffic_ops_golang/api/api.go
index ec3f9f9..d67d914 100644
--- a/traffic_ops/traffic_ops_golang/api/api.go
+++ b/traffic_ops/traffic_ops_golang/api/api.go
@@ -220,7 +220,7 @@ func AllParams(req *http.Request, required []string, ints []string) (map[string]
 }
 
 type ParseValidator interface {
-	Validate(tx *sql.Tx) []error
+	Validate(tx *sql.Tx) error
 }
 
 // Decode decodes a JSON object from r into the given v, validating and sanitizing the input. This helper should be used in API endpoints, rather than the json package, to safely decode and validate PUT and POST requests.
@@ -229,8 +229,8 @@ func Parse(r io.Reader, tx *sql.Tx, v ParseValidator) error {
 	if err := json.NewDecoder(r).Decode(&v); err != nil {
 		return errors.New("decoding: " + err.Error())
 	}
-	if errs := v.Validate(tx); len(errs) > 0 {
-		return errors.New("validating: " + util.JoinErrs(errs).Error())
+	if err := v.Validate(tx); err != nil {
+		return errors.New("validating: " + err.Error())
 	}
 	return nil
 }
diff --git a/traffic_ops/traffic_ops_golang/api/shared_handlers.go b/traffic_ops/traffic_ops_golang/api/shared_handlers.go
index 237cc60..b2b6465 100644
--- a/traffic_ops/traffic_ops_golang/api/shared_handlers.go
+++ b/traffic_ops/traffic_ops_golang/api/shared_handlers.go
@@ -106,11 +106,11 @@ func GetCombinedParams(r *http.Request) (map[string]string, error) {
 //      we lose the ability to unmarshal the struct if a struct implementing the interface is passed in,
 //      because when when it is de-referenced it is a pointer to an interface. A new copy is created so that
 //      there are no issues with concurrent goroutines
-func decodeAndValidateRequestBody(r *http.Request, v Validator) []error {
+func decodeAndValidateRequestBody(r *http.Request, v Validator) error {
 	defer r.Body.Close()
 
 	if err := json.NewDecoder(r.Body).Decode(v); err != nil {
-		return []error{err}
+		return err
 	}
 	return v.Validate()
 }
@@ -248,9 +248,9 @@ func UpdateHandler(typeFactory CRUDFactory) http.HandlerFunc {
 		//create local instance of the shared typeRef pointer
 		//no operations should be made on the typeRef
 		//decode the body and validate the request struct
-		errs := decodeAndValidateRequestBody(r, u)
-		if len(errs) > 0 {
-			handleErrs(http.StatusBadRequest, errs...)
+		err = decodeAndValidateRequestBody(r, u)
+		if err != nil {
+			handleErrs(http.StatusBadRequest, err)
 			return
 		}
 
@@ -435,10 +435,10 @@ func CreateHandler(typeConstructor CRUDFactory) http.HandlerFunc {
 
 		i := typeConstructor(inf)
 		//decode the body and validate the request struct
-		errs := decodeAndValidateRequestBody(r, i)
+		err := decodeAndValidateRequestBody(r, i)
 
-		if len(errs) > 0 {
-			handleErrs(http.StatusBadRequest, errs...)
+		if err != nil {
+			handleErrs(http.StatusBadRequest, err)
 			return
 		}
 
diff --git a/traffic_ops/traffic_ops_golang/api/shared_handlers_test.go b/traffic_ops/traffic_ops_golang/api/shared_handlers_test.go
index f234d8a..abf122f 100644
--- a/traffic_ops/traffic_ops_golang/api/shared_handlers_test.go
+++ b/traffic_ops/traffic_ops_golang/api/shared_handlers_test.go
@@ -74,7 +74,7 @@ func (i *tester) GetAuditName() string {
 }
 
 //Validator interface function
-func (v *tester) Validate() []error {
+func (v *tester) Validate() error {
 	if v.ID < 1 {
 		return []error{errors.New("ID is too low")}
 	}
diff --git a/traffic_ops/traffic_ops_golang/api/shared_interfaces.go b/traffic_ops/traffic_ops_golang/api/shared_interfaces.go
index a8df96f..b1e4635 100644
--- a/traffic_ops/traffic_ops_golang/api/shared_interfaces.go
+++ b/traffic_ops/traffic_ops_golang/api/shared_interfaces.go
@@ -54,7 +54,7 @@ type Deleter interface {
 }
 
 type Validator interface {
-	Validate() []error
+	Validate() error
 }
 
 type Tenantable interface {
diff --git a/traffic_ops/traffic_ops_golang/asn/asns.go b/traffic_ops/traffic_ops_golang/asn/asns.go
index af59b18..3e790a7 100644
--- a/traffic_ops/traffic_ops_golang/asn/asns.go
+++ b/traffic_ops/traffic_ops_golang/asn/asns.go
@@ -29,6 +29,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-log"
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 
@@ -86,12 +87,12 @@ func (asn TOASNV11) GetType() string {
 	return "asn"
 }
 
-func (asn TOASNV11) Validate() []error {
+func (asn TOASNV11) Validate() error {
 	errs := validation.Errors{
 		"asn":          validation.Validate(asn.ASN, validation.NotNil, validation.Min(0)),
 		"cachegroupId": validation.Validate(asn.CachegroupID, validation.NotNil, validation.Min(0)),
 	}
-	return tovalidate.ToErrors(errs)
+	return util.JoinErrs(tovalidate.ToErrors(errs))
 }
 
 //The TOASNV11 implementation of the Creator interface
diff --git a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
index 1e12577..c84e329 100644
--- a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
+++ b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
@@ -29,6 +29,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
 	"github.com/apache/trafficcontrol/lib/go-tc/v13"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 
@@ -146,7 +147,7 @@ func IsValidParentCachegroupID(id *int) bool {
 }
 
 // Validate fulfills the api.Validator interface
-func (cg TOCacheGroup) Validate() []error {
+func (cg TOCacheGroup) Validate() error {
 	validName := validation.NewStringRule(IsValidCacheGroupName, "invalid characters found - Use alphanumeric . or - or _ .")
 	validShortName := validation.NewStringRule(IsValidCacheGroupName, "invalid characters found - Use alphanumeric . or - or _ .")
 	latitudeErr := "Must be a floating point number within the range +-90"
@@ -159,7 +160,7 @@ func (cg TOCacheGroup) Validate() []error {
 		"parentCacheGroupID":          validation.Validate(cg.ParentCachegroupID, validation.Min(1)),
 		"secondaryParentCachegroupID": validation.Validate(cg.SecondaryParentCachegroupID, validation.Min(1)),
 	}
-	return tovalidate.ToErrors(errs)
+	return util.JoinErrs(tovalidate.ToErrors(errs))
 }
 
 // looks up the parent_cachegroup_id and the secondary_cachegroup_id
diff --git a/traffic_ops/traffic_ops_golang/cdn/cdns.go b/traffic_ops/traffic_ops_golang/cdn/cdns.go
index 39e5492..ce67cef 100644
--- a/traffic_ops/traffic_ops_golang/cdn/cdns.go
+++ b/traffic_ops/traffic_ops_golang/cdn/cdns.go
@@ -29,6 +29,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
 	"github.com/apache/trafficcontrol/lib/go-tc/v13"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 
@@ -104,14 +105,14 @@ func IsValidCDNName(str string) bool {
 }
 
 // Validate fulfills the api.Validator interface
-func (cdn TOCDN) Validate() []error {
+func (cdn TOCDN) Validate() error {
 	validName := validation.NewStringRule(IsValidCDNName, "invalid characters found - Use alphanumeric . or - .")
 	validDomainName := validation.NewStringRule(govalidator.IsDNSName, "not a valid domain name")
 	errs := validation.Errors{
 		"name":       validation.Validate(cdn.Name, validation.Required, validName),
 		"domainName": validation.Validate(cdn.DomainName, validation.Required, validDomainName),
 	}
-	return tovalidate.ToErrors(errs)
+	return util.JoinErrs(tovalidate.ToErrors(errs))
 }
 
 //The TOCDN implementation of the Creator interface
diff --git a/traffic_ops/traffic_ops_golang/coordinate/coordinates.go b/traffic_ops/traffic_ops_golang/coordinate/coordinates.go
index 3b978ac..e21dea4 100644
--- a/traffic_ops/traffic_ops_golang/coordinate/coordinates.go
+++ b/traffic_ops/traffic_ops_golang/coordinate/coordinates.go
@@ -29,6 +29,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
 	"github.com/apache/trafficcontrol/lib/go-tc/v13"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 
@@ -104,7 +105,7 @@ func IsValidCoordinateName(str string) bool {
 }
 
 // Validate fulfills the api.Validator interface
-func (coordinate TOCoordinate) Validate() []error {
+func (coordinate TOCoordinate) Validate() error {
 	validName := validation.NewStringRule(IsValidCoordinateName, "invalid characters found - Use alphanumeric . or - or _ .")
 	latitudeErr := "Must be a floating point number within the range +-90"
 	longitudeErr := "Must be a floating point number within the range +-180"
@@ -113,7 +114,7 @@ func (coordinate TOCoordinate) Validate() []error {
 		"latitude":  validation.Validate(coordinate.Latitude, validation.Min(-90.0).Error(latitudeErr), validation.Max(90.0).Error(latitudeErr)),
 		"longitude": validation.Validate(coordinate.Longitude, validation.Min(-180.0).Error(longitudeErr), validation.Max(180.0).Error(longitudeErr)),
 	}
-	return tovalidate.ToErrors(errs)
+	return util.JoinErrs(tovalidate.ToErrors(errs))
 }
 
 //The TOCoordinate implementation of the Creator interface
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
index cd16e7e..e1876bb 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
@@ -170,7 +170,7 @@ func isTenantAuthorized(user *auth.CurrentUser, tx *sqlx.Tx, ds *tc.DeliveryServ
 	return true, nil
 }
 
-func (ds *TODeliveryServiceV12) Validate() []error {
+func (ds *TODeliveryServiceV12) Validate() error {
 	return ds.DeliveryServiceNullableV12.Validate(ds.ReqInfo.Tx.Tx)
 }
 
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
index d6170da..d7b0f7a 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
@@ -89,7 +89,7 @@ func (ds *TODeliveryServiceV13) GetType() string {
 	return ds.V12().GetType()
 }
 
-func (ds *TODeliveryServiceV13) Validate() []error {
+func (ds *TODeliveryServiceV13) Validate() error {
 	return ds.DeliveryServiceNullableV13.Validate(ds.ReqInfo.Tx.Tx)
 }
 
@@ -127,8 +127,8 @@ func CreateV13() http.HandlerFunc {
 		if ds.RoutingName == nil || *ds.RoutingName == "" {
 			ds.RoutingName = util.StrPtr("cdn")
 		}
-		if errs := ds.Validate(inf.Tx.Tx); len(errs) > 0 {
-			api.HandleErr(w, r, http.StatusBadRequest, errors.New("invalid request: "+util.JoinErrs(errs).Error()), nil)
+		if err := ds.Validate(inf.Tx.Tx); err != nil {
+			api.HandleErr(w, r, http.StatusBadRequest, errors.New("invalid request: "+err.Error()), nil)
 			return
 		}
 		if authorized, err := isTenantAuthorized(inf.User, inf.Tx, &ds.DeliveryServiceNullableV12); err != nil {
@@ -401,8 +401,8 @@ func UpdateV13() http.HandlerFunc {
 		}
 		ds.ID = &id
 
-		if errs := ds.Validate(inf.Tx.Tx); len(errs) > 0 {
-			api.HandleErr(w, r, http.StatusBadRequest, errors.New("invalid request: "+util.JoinErrs(errs).Error()), nil)
+		if err := ds.Validate(inf.Tx.Tx); err != nil {
+			api.HandleErr(w, r, http.StatusBadRequest, errors.New("invalid request: "+err.Error()), nil)
 			return
 		}
 
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/request/comment/comments.go b/traffic_ops/traffic_ops_golang/deliveryservice/request/comment/comments.go
index 3818266..cd5972c 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/comment/comments.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/comment/comments.go
@@ -27,6 +27,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-log"
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 
@@ -76,12 +77,12 @@ func (comment TODeliveryServiceRequestComment) GetType() string {
 	return "deliveryservice_request_comment"
 }
 
-func (comment TODeliveryServiceRequestComment) Validate() []error {
+func (comment TODeliveryServiceRequestComment) Validate() error {
 	errs := validation.Errors{
 		"deliveryServiceRequestId": validation.Validate(comment.DeliveryServiceRequestID, validation.NotNil),
 		"value":                    validation.Validate(comment.Value, validation.NotNil),
 	}
-	return tovalidate.ToErrors(errs)
+	return util.JoinErrs(tovalidate.ToErrors(errs))
 }
 
 func (comment *TODeliveryServiceRequestComment) Create() (error, tc.ApiErrorType) {
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go
index 45c0ff9..9dccd08 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go
@@ -514,7 +514,7 @@ func (req *deliveryServiceRequestAssignment) Update() (error, tc.ApiErrorType) {
 	return nil, tc.NoError
 }
 
-func (req deliveryServiceRequestAssignment) Validate() []error {
+func (req deliveryServiceRequestAssignment) Validate() error {
 	return nil
 }
 
@@ -596,7 +596,7 @@ func (req *deliveryServiceRequestStatus) Update() (error, tc.ApiErrorType) {
 }
 
 // Validate is not needed when only Status is updated
-func (req deliveryServiceRequestStatus) Validate() []error {
+func (req deliveryServiceRequestStatus) Validate() error {
 	return nil
 }
 
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/request/validate.go b/traffic_ops/traffic_ops_golang/deliveryservice/request/validate.go
index 2249a43..c9ce200 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/validate.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/validate.go
@@ -26,18 +26,19 @@ import (
 
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
+	"github.com/apache/trafficcontrol/lib/go-util"
 
 	"github.com/go-ozzo/ozzo-validation"
 )
 
 // Validate ensures all required fields are present and in correct form.  Also checks request JSON is complete and valid
-func (req *TODeliveryServiceRequest) Validate() []error {
+func (req *TODeliveryServiceRequest) Validate() error {
 	fromStatus := tc.RequestStatusDraft
 	if req.ID != nil && *req.ID > 0 {
 		err := req.ReqInfo.Tx.QueryRow(`SELECT status FROM deliveryservice_request WHERE id=` + strconv.Itoa(*req.ID)).Scan(&fromStatus)
 
 		if err != nil {
-			return []error{err}
+			return err
 		}
 	}
 
@@ -61,7 +62,7 @@ func (req *TODeliveryServiceRequest) Validate() []error {
 	// ensure the deliveryservice requested is valid
 	e := req.DeliveryService.Validate(req.ReqInfo.Tx.Tx)
 
-	errs = append(errs, e...)
+	errs = append(errs, e)
 
-	return errs
+	return util.JoinErrs(errs)
 }
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go
index 177408c..d1e3018 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go
@@ -29,6 +29,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-log"
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
@@ -91,14 +92,14 @@ func (dss *TODeliveryServiceServer) SetKeys(keys map[string]interface{}) {
 }
 
 // Validate fulfills the api.Validator interface
-func (dss *TODeliveryServiceServer) Validate(db *sqlx.DB) []error {
+func (dss *TODeliveryServiceServer) Validate(db *sqlx.DB) error {
 
 	errs := validation.Errors{
 		"deliveryservice": validation.Validate(dss.DeliveryService, validation.Required),
 		"server":          validation.Validate(dss.Server, validation.Required),
 	}
 
-	return tovalidate.ToErrors(errs)
+	return util.JoinErrs(tovalidate.ToErrors(errs))
 }
 
 // ReadDSSHandler list all of the Deliveryservice Servers in response to requests to api/1.1/deliveryserviceserver$
diff --git a/traffic_ops/traffic_ops_golang/division/divisions.go b/traffic_ops/traffic_ops_golang/division/divisions.go
index 52a8e4c..810f6ce 100644
--- a/traffic_ops/traffic_ops_golang/division/divisions.go
+++ b/traffic_ops/traffic_ops_golang/division/divisions.go
@@ -28,6 +28,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-log"
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 
@@ -79,11 +80,11 @@ func (division TODivision) GetType() string {
 	return "division"
 }
 
-func (division TODivision) Validate() []error {
+func (division TODivision) Validate() error {
 	errs := validation.Errors{
 		"name": validation.Validate(division.Name, validation.NotNil, validation.Required),
 	}
-	return tovalidate.ToErrors(errs)
+	return util.JoinErrs(tovalidate.ToErrors(errs))
 }
 
 //The TODivision implementation of the Creator interface
diff --git a/traffic_ops/traffic_ops_golang/origin/origins.go b/traffic_ops/traffic_ops_golang/origin/origins.go
index 82eb3ea..0693ff5 100644
--- a/traffic_ops/traffic_ops_golang/origin/origins.go
+++ b/traffic_ops/traffic_ops_golang/origin/origins.go
@@ -29,6 +29,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
 	"github.com/apache/trafficcontrol/lib/go-tc/v13"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
@@ -88,7 +89,7 @@ func (origin *TOOrigin) GetType() string {
 	return "origin"
 }
 
-func (origin *TOOrigin) Validate() []error {
+func (origin *TOOrigin) Validate() error {
 
 	noSpaces := validation.NewStringRule(tovalidate.NoSpaces, "cannot contain spaces")
 	validProtocol := validation.NewStringRule(tovalidate.IsOneOfStringICase("http", "https"), "must be http or https")
@@ -107,7 +108,7 @@ func (origin *TOOrigin) Validate() []error {
 		"protocol":          validation.Validate(origin.Protocol, validation.Required, validProtocol),
 		"tenantId":          validation.Validate(origin.TenantID, validation.Min(1)),
 	}
-	return tovalidate.ToErrors(validateErrs)
+	return util.JoinErrs(tovalidate.ToErrors(validateErrs))
 }
 
 // GetTenantID returns a pointer to the Origin's tenant ID from the Tx and any error encountered
diff --git a/traffic_ops/traffic_ops_golang/parameter/parameters.go b/traffic_ops/traffic_ops_golang/parameter/parameters.go
index e21011d..3217d93 100644
--- a/traffic_ops/traffic_ops_golang/parameter/parameters.go
+++ b/traffic_ops/traffic_ops_golang/parameter/parameters.go
@@ -27,6 +27,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-log"
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
@@ -93,7 +94,7 @@ func (param *TOParameter) GetType() string {
 }
 
 // Validate fulfills the api.Validator interface
-func (param TOParameter) Validate() []error {
+func (param TOParameter) Validate() error {
 	// Test
 	// - Secure Flag is always set to either 1/0
 	// - Admin rights only
@@ -104,7 +105,7 @@ func (param TOParameter) Validate() []error {
 		ValueQueryParam:      validation.Validate(param.Value, validation.Required),
 	}
 
-	return tovalidate.ToErrors(errs)
+	return util.JoinErrs(tovalidate.ToErrors(errs))
 }
 
 //The TOParameter implementation of the Creator interface
diff --git a/traffic_ops/traffic_ops_golang/physlocation/phys_locations.go b/traffic_ops/traffic_ops_golang/physlocation/phys_locations.go
index b9de171..c2c86ba 100644
--- a/traffic_ops/traffic_ops_golang/physlocation/phys_locations.go
+++ b/traffic_ops/traffic_ops_golang/physlocation/phys_locations.go
@@ -27,6 +27,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-log"
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 
@@ -79,7 +80,7 @@ func (pl *TOPhysLocation) GetType() string {
 	return "physLocation"
 }
 
-func (pl *TOPhysLocation) Validate() []error {
+func (pl *TOPhysLocation) Validate() error {
 	errs := validation.Errors{
 		"address":   validation.Validate(pl.Address, validation.Required),
 		"city":      validation.Validate(pl.City, validation.Required),
@@ -90,7 +91,7 @@ func (pl *TOPhysLocation) Validate() []error {
 		"zip":       validation.Validate(pl.Zip, validation.Required),
 	}
 	if errs != nil {
-		return tovalidate.ToErrors(errs)
+		return util.JoinErrs(tovalidate.ToErrors(errs))
 	}
 	return nil
 }
diff --git a/traffic_ops/traffic_ops_golang/profile/profiles.go b/traffic_ops/traffic_ops_golang/profile/profiles.go
index b3cbaa2..a522eb5 100644
--- a/traffic_ops/traffic_ops_golang/profile/profiles.go
+++ b/traffic_ops/traffic_ops_golang/profile/profiles.go
@@ -28,6 +28,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
 	"github.com/apache/trafficcontrol/lib/go-tc/v13"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
@@ -90,7 +91,7 @@ func (prof *TOProfile) GetType() string {
 	return "profile"
 }
 
-func (prof *TOProfile) Validate() []error {
+func (prof *TOProfile) Validate() error {
 	errs := validation.Errors{
 		NameQueryParam:        validation.Validate(prof.Name, validation.Required),
 		DescriptionQueryParam: validation.Validate(prof.Description, validation.Required),
@@ -98,7 +99,7 @@ func (prof *TOProfile) Validate() []error {
 		TypeQueryParam:        validation.Validate(prof.Type, validation.Required),
 	}
 	if errs != nil {
-		return tovalidate.ToErrors(errs)
+		return util.JoinErrs(tovalidate.ToErrors(errs))
 	}
 	return nil
 }
diff --git a/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go b/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go
index 9d3c7b7..2eb33c6 100644
--- a/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go
+++ b/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go
@@ -28,6 +28,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
 	"github.com/apache/trafficcontrol/lib/go-tc/v13"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 
@@ -95,14 +96,14 @@ func (pp *TOProfileParameter) SetKeys(keys map[string]interface{}) {
 }
 
 // Validate fulfills the api.Validator interface
-func (pp *TOProfileParameter) Validate() []error {
+func (pp *TOProfileParameter) Validate() error {
 
 	errs := validation.Errors{
 		"profile":   validation.Validate(pp.ProfileID, validation.Required),
 		"parameter": validation.Validate(pp.ParameterID, validation.Required),
 	}
 
-	return tovalidate.ToErrors(errs)
+	return util.JoinErrs(tovalidate.ToErrors(errs))
 }
 
 //The TOProfileParameter implementation of the Creator interface
diff --git a/traffic_ops/traffic_ops_golang/region/regions.go b/traffic_ops/traffic_ops_golang/region/regions.go
index a8d074b..abeec54 100644
--- a/traffic_ops/traffic_ops_golang/region/regions.go
+++ b/traffic_ops/traffic_ops_golang/region/regions.go
@@ -66,12 +66,11 @@ func (region *TORegion) GetType() string {
 	return "region"
 }
 
-func (region *TORegion) Validate() []error {
-	errs := []error{}
+func (region *TORegion) Validate() error {
 	if len(region.Name) < 1 {
-		errs = append(errs, errors.New(`Region 'name' is required.`))
+		return errors.New(`Region 'name' is required.`)
 	}
-	return errs
+	return nil
 }
 
 func (region *TORegion) Read(parameters map[string]string) ([]interface{}, []error, tc.ApiErrorType) {
diff --git a/traffic_ops/traffic_ops_golang/role/roles.go b/traffic_ops/traffic_ops_golang/role/roles.go
index ba5916f..72c2904 100644
--- a/traffic_ops/traffic_ops_golang/role/roles.go
+++ b/traffic_ops/traffic_ops_golang/role/roles.go
@@ -28,6 +28,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
 	"github.com/apache/trafficcontrol/lib/go-tc/v13"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 
@@ -81,7 +82,7 @@ func (role *TORole) SetKeys(keys map[string]interface{}) {
 }
 
 // Validate fulfills the api.Validator interface
-func (role TORole) Validate() []error {
+func (role TORole) Validate() error {
 	errs := validation.Errors{
 		"name":        validation.Validate(role.Name, validation.Required),
 		"description": validation.Validate(role.Description, validation.Required),
@@ -94,13 +95,13 @@ func (role TORole) Validate() []error {
 		err := role.ReqInfo.Tx.Select(&badCaps, checkCaps, pq.Array(role.Capabilities))
 		if err != nil {
 			log.Errorf("got error from selecting bad capabilities: %v", err)
-			return []error{tc.DBError}
+			return tc.DBError
 		}
 		if len(badCaps) > 0 {
 			errsToReturn = append(errsToReturn, fmt.Errorf("can not add non-existent capabilities: %v", badCaps))
 		}
 	}
-	return errsToReturn
+	return util.JoinErrs(errsToReturn)
 }
 
 //The TORole implementation of the Creator interface
diff --git a/traffic_ops/traffic_ops_golang/server/servers.go b/traffic_ops/traffic_ops_golang/server/servers.go
index 879d165..91ef619 100644
--- a/traffic_ops/traffic_ops_golang/server/servers.go
+++ b/traffic_ops/traffic_ops_golang/server/servers.go
@@ -28,6 +28,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
 	"github.com/apache/trafficcontrol/lib/go-tc/v13"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
@@ -82,7 +83,7 @@ func (server *TOServer) GetType() string {
 	return "server"
 }
 
-func (server *TOServer) Validate() []error {
+func (server *TOServer) Validate() error {
 
 	noSpaces := validation.NewStringRule(tovalidate.NoSpaces, "cannot contain spaces")
 
@@ -104,22 +105,20 @@ func (server *TOServer) Validate() []error {
 	}
 	errs := tovalidate.ToErrors(validateErrs)
 	if len(errs) > 0 {
-		return errs
+		return util.JoinErrs(errs)
 	}
 
 	rows, err := server.ReqInfo.Tx.Query("select use_in_table from type where id=$1", server.TypeID)
 	if err != nil {
 		log.Error.Printf("could not execute select use_in_table from type: %s\n", err)
-		errs = append(errs, tc.DBError)
-		return errs
+		return tc.DBError
 	}
 	defer rows.Close()
 	var useInTable string
 	for rows.Next() {
 		if err := rows.Scan(&useInTable); err != nil {
 			log.Error.Printf("could not scan use_in_table from type: %s\n", err)
-			errs = append(errs, tc.DBError)
-			return errs
+			return tc.DBError
 		}
 	}
 	if useInTable != "server" {
@@ -130,7 +129,7 @@ func (server *TOServer) Validate() []error {
 	if err != nil {
 		log.Error.Printf("could not execute select cdnID from profile: %s\n", err)
 		errs = append(errs, tc.DBError)
-		return errs
+		return util.JoinErrs(errs)
 	}
 	defer rows.Close()
 	var cdnID int
@@ -138,14 +137,14 @@ func (server *TOServer) Validate() []error {
 		if err := rows.Scan(&cdnID); err != nil {
 			log.Error.Printf("could not scan cdnID from profile: %s\n", err)
 			errs = append(errs, tc.DBError)
-			return errs
+			return util.JoinErrs(errs)
 		}
 	}
 	log.Infof("got cdn id: %d from profile and cdn id: %d from server", cdnID, *server.CDNID)
 	if cdnID != *server.CDNID {
 		errs = append(errs, errors.New(fmt.Sprintf("CDN id '%d' for profile '%d' does not match Server CDN '%d'", cdnID, *server.ProfileID, *server.CDNID)))
 	}
-	return errs
+	return util.JoinErrs(errs)
 }
 
 // ChangeLogMessage implements the api.ChangeLogger interface for a custom log message
diff --git a/traffic_ops/traffic_ops_golang/status/statuses.go b/traffic_ops/traffic_ops_golang/status/statuses.go
index 9c405af..fb36b8f 100644
--- a/traffic_ops/traffic_ops_golang/status/statuses.go
+++ b/traffic_ops/traffic_ops_golang/status/statuses.go
@@ -27,6 +27,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-log"
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 
@@ -79,11 +80,11 @@ func (status TOStatus) GetType() string {
 	return "status"
 }
 
-func (status TOStatus) Validate() []error {
+func (status TOStatus) Validate() error {
 	errs := validation.Errors{
 		"name": validation.Validate(status.Name, validation.NotNil, validation.Required),
 	}
-	return tovalidate.ToErrors(errs)
+	return util.JoinErrs(tovalidate.ToErrors(errs))
 }
 
 func (status *TOStatus) Read(parameters map[string]string) ([]interface{}, []error, tc.ApiErrorType) {
diff --git a/traffic_ops/traffic_ops_golang/tenant/tenancy.go b/traffic_ops/traffic_ops_golang/tenant/tenancy.go
index 202dd70..51f41ad 100644
--- a/traffic_ops/traffic_ops_golang/tenant/tenancy.go
+++ b/traffic_ops/traffic_ops_golang/tenant/tenancy.go
@@ -352,13 +352,13 @@ func (ten *TOTenant) SetKeys(keys map[string]interface{}) {
 }
 
 // Validate fulfills the api.Validator interface
-func (ten TOTenant) Validate() []error {
+func (ten TOTenant) Validate() error {
 	errs := validation.Errors{
 		"name":     validation.Validate(ten.Name, validation.Required),
 		"active":   validation.Validate(ten.Active), // only validate it's boolean
 		"parentId": validation.Validate(ten.ParentID, validation.Required, validation.Min(1)),
 	}
-	return tovalidate.ToErrors(errs)
+	return util.JoinErrs(tovalidate.ToErrors(errs))
 }
 
 // Create implements the Creator interface
diff --git a/traffic_ops/traffic_ops_golang/types/types.go b/traffic_ops/traffic_ops_golang/types/types.go
index a8c809f..4493ab7 100644
--- a/traffic_ops/traffic_ops_golang/types/types.go
+++ b/traffic_ops/traffic_ops_golang/types/types.go
@@ -27,6 +27,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-log"
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
+	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 
@@ -79,14 +80,14 @@ func (typ *TOType) GetType() string {
 	return "type"
 }
 
-func (typ *TOType) Validate() []error {
+func (typ *TOType) Validate() error {
 	errs := validation.Errors{
 		"name":         validation.Validate(typ.Name, validation.Required),
 		"description":  validation.Validate(typ.Description, validation.Required),
 		"use_in_table": validation.Validate(typ.UseInTable, validation.Required),
 	}
 	if errs != nil {
-		return tovalidate.ToErrors(errs)
+		return util.JoinErrs(tovalidate.ToErrors(errs))
 	}
 	return nil
 }