You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ra...@apache.org on 2022/05/18 20:09:54 UTC

[trafficcontrol] branch master updated: Remove tc.DBError, make api.Validators return two errors (#6770)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new f4c3f770ba Remove tc.DBError, make api.Validators return two errors (#6770)
f4c3f770ba is described below

commit f4c3f770ba45c2a4fc1d05e9bace3ec39799380e
Author: ocket8888 <oc...@apache.org>
AuthorDate: Wed May 18 14:09:49 2022 -0600

    Remove tc.DBError, make api.Validators return two errors (#6770)
---
 lib/go-tc/constants.go                             |  5 --
 lib/go-tc/steeringtarget.go                        |  6 +-
 .../traffic_ops_golang/api/shared_handlers.go      | 40 ++++++----
 .../traffic_ops_golang/api/shared_handlers_test.go |  6 +-
 .../traffic_ops_golang/api/shared_interfaces.go    |  9 ++-
 traffic_ops/traffic_ops_golang/apitenant/tenant.go |  6 +-
 traffic_ops/traffic_ops_golang/asn/asns.go         |  4 +-
 traffic_ops/traffic_ops_golang/asn/asns_test.go    |  3 +-
 .../traffic_ops_golang/cachegroup/cachegroups.go   | 33 ++++----
 .../cachegroup/cachegroups_test.go                 | 10 ++-
 traffic_ops/traffic_ops_golang/cdn/cdns.go         |  6 +-
 traffic_ops/traffic_ops_golang/cdn/cdns_test.go    |  5 +-
 .../cdnfederation/cdnfederations.go                | 12 +--
 .../traffic_ops_golang/coordinate/coordinates.go   |  6 +-
 .../coordinate/coordinates_test.go                 |  5 +-
 .../deliveryservice/deliveryservices.go            |  3 +-
 .../deliveryservices_required_capabilities.go      | 12 +--
 .../deliveryservice/request/comment/comments.go    |  6 +-
 .../request/comment/comments_test.go               |  5 +-
 .../traffic_ops_golang/division/divisions.go       |  4 +-
 .../traffic_ops_golang/division/divisions_test.go  |  3 +-
 traffic_ops/traffic_ops_golang/origin/origins.go   |  7 +-
 .../traffic_ops_golang/origin/origins_test.go      |  8 +-
 .../traffic_ops_golang/parameter/parameters.go     |  4 +-
 .../physlocation/phys_locations.go                 |  6 +-
 .../physlocation/phys_locations_test.go            |  4 +-
 traffic_ops/traffic_ops_golang/profile/profiles.go |  6 +-
 .../traffic_ops_golang/profile/profiles_test.go    |  5 +-
 .../profileparameter/profile_parameters.go         |  6 +-
 traffic_ops/traffic_ops_golang/region/regions.go   |  8 +-
 .../traffic_ops_golang/region/regions_test.go      |  6 +-
 traffic_ops/traffic_ops_golang/role/roles.go       |  7 +-
 traffic_ops/traffic_ops_golang/role/roles_test.go  | 12 ++-
 traffic_ops/traffic_ops_golang/server/servers.go   | 89 +++++++++++++---------
 .../server/servers_assignment.go                   |  3 +-
 .../server/servers_server_capability.go            |  8 +-
 .../traffic_ops_golang/server/servers_test.go      | 18 ++---
 .../server/servers_update_status.go                | 16 ++--
 .../server/servers_update_status_test.go           |  2 +-
 .../servercapability/servercapability.go           |  4 +-
 .../servicecategory/servicecategories.go           | 14 ++--
 .../staticdnsentry/staticdnsentry.go               |  8 +-
 .../staticdnsentry/staticdnsentry_test.go          |  3 +-
 traffic_ops/traffic_ops_golang/status/statuses.go  |  4 +-
 .../steeringtargets/steeringtargets.go             |  2 +-
 .../steeringtargets/steeringtargets_test.go        |  5 +-
 .../traffic_ops_golang/topology/topologies.go      | 26 ++-----
 traffic_ops/traffic_ops_golang/types/types.go      |  6 +-
 traffic_ops/traffic_ops_golang/types/types_test.go |  3 +-
 traffic_ops/traffic_ops_golang/user/user.go        |  8 +-
 50 files changed, 264 insertions(+), 223 deletions(-)

diff --git a/lib/go-tc/constants.go b/lib/go-tc/constants.go
index 3ccfb66967..188d604bed 100644
--- a/lib/go-tc/constants.go
+++ b/lib/go-tc/constants.go
@@ -34,11 +34,6 @@ type ErrorConstant string
 // Error converts ErrorConstants to a string.
 func (e ErrorConstant) Error() string { return string(e) }
 
-// DBError is an error message for database errors.
-//
-// Deprecated: Since internal errors are not returned to users, there's no reason not to include more detail in an error message than this.
-const DBError = ErrorConstant("database access error")
-
 // NilTenantError can used when a Tenantable object finds that TentantID in the
 // request is nil.
 const NilTenantError = ErrorConstant("tenancy is enabled but request tenantID is nil")
diff --git a/lib/go-tc/steeringtarget.go b/lib/go-tc/steeringtarget.go
index 8dc13676bd..dbc836225b 100644
--- a/lib/go-tc/steeringtarget.go
+++ b/lib/go-tc/steeringtarget.go
@@ -58,7 +58,7 @@ type SteeringTargetNullable struct {
 // Validate implements the
 // github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api.ParseValidator
 // interface.
-func (st SteeringTargetNullable) Validate(tx *sql.Tx) error {
+func (st SteeringTargetNullable) Validate(tx *sql.Tx) (error, error) {
 	errs := []string{}
 	if st.TypeID == nil {
 		errs = append(errs, "missing typeId")
@@ -71,9 +71,9 @@ func (st SteeringTargetNullable) Validate(tx *sql.Tx) error {
 		errs = append(errs, "missing value")
 	}
 	if len(errs) > 0 {
-		return errors.New(strings.Join(errs, "; "))
+		return errors.New(strings.Join(errs, "; ")), nil
 	}
-	return nil
+	return nil, nil
 }
 
 // SteeringTargetsResponse is the type of a response from Traffic Ops to its
diff --git a/traffic_ops/traffic_ops_golang/api/shared_handlers.go b/traffic_ops/traffic_ops_golang/api/shared_handlers.go
index a657b27f29..2e86dff6eb 100644
--- a/traffic_ops/traffic_ops_golang/api/shared_handlers.go
+++ b/traffic_ops/traffic_ops_golang/api/shared_handlers.go
@@ -103,11 +103,11 @@ func GetCombinedParams(r *http.Request) (map[string]string, error) {
 }
 
 // decodeAndValidateRequestBody decodes and validates a pointer to a struct implementing the Validator interface
-func decodeAndValidateRequestBody(r *http.Request, v Validator) error {
+func decodeAndValidateRequestBody(r *http.Request, v Validator) (error, error) {
 	defer r.Body.Close()
 
 	if err := json.NewDecoder(r.Body).Decode(v); err != nil {
-		return err
+		return err, nil
 	}
 	return v.Validate()
 }
@@ -242,8 +242,12 @@ func UpdateHandler(updater Updater) http.HandlerFunc {
 		obj := reflect.New(objectType).Interface().(Updater)
 		obj.SetInfo(inf)
 
-		if err := decodeAndValidateRequestBody(r, obj); err != nil {
-			HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, err, nil)
+		if userErr, sysErr := decodeAndValidateRequestBody(r, obj); userErr != nil || sysErr != nil {
+			code := http.StatusBadRequest
+			if sysErr != nil {
+				code = http.StatusInternalServerError
+			}
+			HandleErr(w, r, inf.Tx.Tx, code, userErr, sysErr)
 			return
 		}
 
@@ -298,7 +302,7 @@ func UpdateHandler(updater Updater) http.HandlerFunc {
 		}
 
 		if err := CreateChangeLog(ApiChange, Updated, obj, inf.User, inf.Tx.Tx); err != nil {
-			HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, tc.DBError, errors.New("inserting changelog: "+err.Error()))
+			HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("inserting changelog: %w", err))
 			return
 		}
 		alerts := tc.CreateAlerts(tc.SuccessLevel, obj.GetType()+" was updated.")
@@ -489,9 +493,13 @@ func CreateHandler(creator Creator) http.HandlerFunc {
 			for _, objElemInt := range objSlice {
 				objElem := reflect.ValueOf(objElemInt).Interface().(Creator)
 
-				err = objElem.Validate()
-				if err != nil {
-					HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, err, nil)
+				userErr, sysErr = objElem.Validate()
+				if userErr != nil || sysErr != nil {
+					code := http.StatusBadRequest
+					if sysErr != nil {
+						code = http.StatusInternalServerError
+					}
+					HandleErr(w, r, inf.Tx.Tx, code, userErr, sysErr)
 					return
 				}
 
@@ -514,7 +522,7 @@ func CreateHandler(creator Creator) http.HandlerFunc {
 				}
 
 				if err = CreateChangeLog(ApiChange, Created, objElem, inf.User, inf.Tx.Tx); err != nil {
-					HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, tc.DBError, errors.New("inserting changelog: "+err.Error()))
+					HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("inserting changelog: %w", err))
 					return
 				}
 			}
@@ -541,9 +549,13 @@ func CreateHandler(creator Creator) http.HandlerFunc {
 			WriteAlertsObj(w, r, http.StatusOK, alerts, responseObj)
 
 		} else {
-			err := decodeAndValidateRequestBody(r, obj)
-			if err != nil {
-				HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, err, nil)
+			userErr, sysErr := decodeAndValidateRequestBody(r, obj)
+			if userErr != nil || sysErr != nil {
+				code := http.StatusBadRequest
+				if sysErr != nil {
+					code = http.StatusInternalServerError
+				}
+				HandleErr(w, r, inf.Tx.Tx, code, userErr, sysErr)
 				return
 			}
 
@@ -565,8 +577,8 @@ func CreateHandler(creator Creator) http.HandlerFunc {
 				return
 			}
 
-			if err = CreateChangeLog(ApiChange, Created, obj, inf.User, inf.Tx.Tx); err != nil {
-				HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, tc.DBError, errors.New("inserting changelog: "+err.Error()))
+			if err := CreateChangeLog(ApiChange, Created, obj, inf.User, inf.Tx.Tx); err != nil {
+				HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("inserting changelog: %w", err))
 				return
 			}
 			alerts := tc.CreateAlerts(tc.SuccessLevel, obj.GetType()+" was created.")
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 2a26e18f38..51675ee5b2 100644
--- a/traffic_ops/traffic_ops_golang/api/shared_handlers_test.go
+++ b/traffic_ops/traffic_ops_golang/api/shared_handlers_test.go
@@ -72,11 +72,11 @@ func (i *tester) GetAuditName() string {
 }
 
 //Validator interface function
-func (v *tester) Validate() error {
+func (v *tester) Validate() (error, error) {
 	if v.ID < 1 {
-		return errors.New("ID is too low")
+		return errors.New("ID is too low"), nil
 	}
-	return nil
+	return nil, nil
 }
 
 //Creator interface functions
diff --git a/traffic_ops/traffic_ops_golang/api/shared_interfaces.go b/traffic_ops/traffic_ops_golang/api/shared_interfaces.go
index d9e46fab95..1d92773935 100644
--- a/traffic_ops/traffic_ops_golang/api/shared_interfaces.go
+++ b/traffic_ops/traffic_ops_golang/api/shared_interfaces.go
@@ -20,11 +20,12 @@ package api
  */
 
 import (
+	"net/http"
+	"time"
+
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
-	"net/http"
-	"time"
 )
 
 type CRUDer interface {
@@ -103,8 +104,10 @@ type OptionsDeleter interface {
 	DeleteKeyOptions() map[string]dbhelpers.WhereColumnInfo
 }
 
+// Validator objects return user and system errors based on validation rules
+// defined by that object.
 type Validator interface {
-	Validate() error
+	Validate() (error, error)
 }
 
 type Tenantable interface {
diff --git a/traffic_ops/traffic_ops_golang/apitenant/tenant.go b/traffic_ops/traffic_ops_golang/apitenant/tenant.go
index 49c1d75584..929656490a 100644
--- a/traffic_ops/traffic_ops_golang/apitenant/tenant.go
+++ b/traffic_ops/traffic_ops_golang/apitenant/tenant.go
@@ -121,15 +121,15 @@ func (ten *TOTenant) SetKeys(keys map[string]interface{}) {
 	ten.ID = &i
 }
 
-// Validate fulfills the api.Validator interface
-func (ten TOTenant) Validate() error {
+// Validate fulfills the api.Validator interface.
+func (ten TOTenant) Validate() (error, 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)),
 		"parentName": nil,
 	}
-	return util.JoinErrs(tovalidate.ToErrors(errs))
+	return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 }
 
 func (ten *TOTenant) Create() (error, error, int) { return api.GenericCreate(ten) }
diff --git a/traffic_ops/traffic_ops_golang/asn/asns.go b/traffic_ops/traffic_ops_golang/asn/asns.go
index 3e96117321..2c7ec6a871 100644
--- a/traffic_ops/traffic_ops_golang/asn/asns.go
+++ b/traffic_ops/traffic_ops_golang/asn/asns.go
@@ -95,12 +95,12 @@ func (asn TOASNV11) GetType() string {
 	return "asn"
 }
 
-func (asn TOASNV11) Validate() error {
+func (asn TOASNV11) Validate() (error, 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 util.JoinErrs(tovalidate.ToErrors(errs))
+	return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 }
 
 func (as *TOASNV11) Create() (error, error, int) {
diff --git a/traffic_ops/traffic_ops_golang/asn/asns_test.go b/traffic_ops/traffic_ops_golang/asn/asns_test.go
index 67410c50f0..62a46af7f8 100644
--- a/traffic_ops/traffic_ops_golang/asn/asns_test.go
+++ b/traffic_ops/traffic_ops_golang/asn/asns_test.go
@@ -125,7 +125,8 @@ func TestValidate(t *testing.T) {
 		api.APIInfoImpl{},
 		tc.ASNNullable{ASN: &i, CachegroupID: &i},
 	}
-	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(asn.Validate())))
+	err, _ := asn.Validate()
+	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(err)))
 	expected := util.JoinErrsStr([]error{
 		errors.New(`'asn' must be no less than 0`),
 		errors.New(`'cachegroupId' must be no less than 0`),
diff --git a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
index 155934635b..46355e8fc3 100644
--- a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
+++ b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
@@ -247,28 +247,31 @@ func (cg *TOCacheGroup) ValidateTypeInTopology() error {
 	return fmt.Errorf("cannot change type of cachegroup %s from %s to %s because it is in use by a topology", *cg.Name, typeNameByID[previousTypeID], typeNameByID[*cg.TypeID])
 }
 
-// Validate fulfills the api.Validator interface
-func (cg TOCacheGroup) Validate() error {
+// Validate fulfills the api.Validator interface.
+//
+// TODO: A lot of database operations here either swallow their errors or return
+// them to the client.
+func (cg TOCacheGroup) Validate() (error, error) {
 	if _, err := tc.ValidateTypeID(cg.ReqInfo.Tx.Tx, cg.TypeID, "cachegroup"); err != nil {
-		return err
+		return err, nil
 	}
 
 	if cg.Fallbacks != nil && len(*cg.Fallbacks) > 0 {
 		isValid, err := cg.isAllowedToFallback(*cg.TypeID)
 		if err != nil {
-			return err
+			return err, nil
 		}
 		if !isValid {
-			return errors.New("the cache group " + *cg.Name + " is not allowed to have fallbacks.  It must be of type EDGE_LOC.")
+			return errors.New("the cache group " + *cg.Name + " is not allowed to have fallbacks. It must be of type EDGE_LOC."), nil
 		}
 
 		for _, fallback := range *cg.Fallbacks {
 			isValid, err = cg.isValidCacheGroupFallback(fallback)
 			if err != nil {
-				return err
+				return err, nil
 			}
 			if !isValid {
-				return errors.New("the cache group " + fallback + " is not valid as a fallback.  It must exist as a cache group and be of type EDGE_LOC.")
+				return errors.New("the cache group " + fallback + " is not valid as a fallback. It must exist as a cache group and be of type EDGE_LOC."), nil
 			}
 		}
 	}
@@ -287,7 +290,7 @@ func (cg TOCacheGroup) Validate() error {
 		"localizationMethods":         validation.Validate(cg.LocalizationMethods, validation.By(tovalidate.IsPtrToSliceOfUniqueStringersICase("CZ", "DEEP_CZ", "GEO"))),
 		"type":                        cg.ValidateTypeInTopology(),
 	}
-	return util.JoinErrs(tovalidate.ToErrors(errs))
+	return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 }
 
 //The TOCacheGroup implementation of the Creator interface
@@ -393,10 +396,10 @@ func (cg *TOCacheGroup) createCacheGroupFallbacks() error {
 func (cg *TOCacheGroup) isValidCacheGroupFallback(fallbackName string) (bool, error) {
 	var isValid bool
 	query := `SELECT(
-SELECT cachegroup.id 
-FROM cachegroup 
-JOIN type on type.id = cachegroup.type 
-WHERE cachegroup.name = $1 
+SELECT cachegroup.id
+FROM cachegroup
+JOIN type on type.id = cachegroup.type
+WHERE cachegroup.name = $1
 AND (type.name = 'EDGE_LOC')
 ) IS NOT NULL;`
 
@@ -411,9 +414,9 @@ AND (type.name = 'EDGE_LOC')
 func (cg *TOCacheGroup) isAllowedToFallback(cacheGroupType int) (bool, error) {
 	var isValid bool
 	query := `SELECT(
-SELECT type.name 
-FROM type 
-WHERE type.id = $1 
+SELECT type.name
+FROM type
+WHERE type.id = $1
 AND (type.name = 'EDGE_LOC')
 ) IS NOT NULL;`
 
diff --git a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go
index ae3c701102..601f05af1a 100644
--- a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go
+++ b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go
@@ -231,7 +231,8 @@ func TestValidate(t *testing.T) {
 			LastUpdated:         &lu,
 		},
 	}
-	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(c.Validate())))
+	err, _ = c.Validate()
+	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(err)))
 
 	expectedErrs := util.JoinErrsStr([]error{
 		errors.New(`'latitude' Must be a floating point number within the range +-90`),
@@ -272,9 +273,12 @@ func TestValidate(t *testing.T) {
 			LastUpdated:         &lu,
 		},
 	}
-	err = c.Validate()
+	err, sysErr := c.Validate()
 	if err != nil {
-		t.Errorf("expected nil, got %s", err)
+		t.Errorf("expected nil user error, got: %s", err)
+	}
+	if sysErr != nil {
+		t.Errorf("expected nil system error, got: %s", sysErr)
 	}
 }
 
diff --git a/traffic_ops/traffic_ops_golang/cdn/cdns.go b/traffic_ops/traffic_ops_golang/cdn/cdns.go
index 79ccdd01a2..e98a4b92a0 100644
--- a/traffic_ops/traffic_ops_golang/cdn/cdns.go
+++ b/traffic_ops/traffic_ops_golang/cdn/cdns.go
@@ -120,15 +120,15 @@ func IsValidCDNName(str string) bool {
 	return i == -1
 }
 
-// Validate fulfills the api.Validator interface
-func (cdn TOCDN) Validate() error {
+// Validate fulfills the api.Validator interface.
+func (cdn TOCDN) Validate() (error, 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 util.JoinErrs(tovalidate.ToErrors(errs))
+	return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 }
 
 func (cdn *TOCDN) Create() (error, error, int) {
diff --git a/traffic_ops/traffic_ops_golang/cdn/cdns_test.go b/traffic_ops/traffic_ops_golang/cdn/cdns_test.go
index 7830f550e8..baa10197c3 100644
--- a/traffic_ops/traffic_ops_golang/cdn/cdns_test.go
+++ b/traffic_ops/traffic_ops_golang/cdn/cdns_test.go
@@ -136,7 +136,8 @@ func TestValidate(t *testing.T) {
 	// invalid name, empty domainname
 	n := "not_a_valid_cdn"
 	c := TOCDN{CDNNullable: tc.CDNNullable{Name: &n}}
-	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(c.Validate())))
+	err, _ := c.Validate()
+	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(err)))
 
 	expectedErrs := util.JoinErrsStr([]error{
 		errors.New(`'domainName' cannot be blank`),
@@ -151,7 +152,7 @@ func TestValidate(t *testing.T) {
 	n = "This.is.2.a-Valid---CDNNAME."
 	d := `awesome-cdn.example.net`
 	c = TOCDN{CDNNullable: tc.CDNNullable{Name: &n, DomainName: &d}}
-	err := c.Validate()
+	err, _ = c.Validate()
 	if err != nil {
 		t.Errorf("expected nil, got %s", err)
 	}
diff --git a/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations.go b/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations.go
index 54a218e290..b15b61283b 100644
--- a/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations.go
+++ b/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations.go
@@ -35,7 +35,7 @@ import (
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
 	"github.com/asaskevich/govalidator"
-	"github.com/go-ozzo/ozzo-validation"
+	validation "github.com/go-ozzo/ozzo-validation"
 )
 
 // we need a type alias to define functions on
@@ -53,9 +53,9 @@ func (v *TOCDNFederation) SetLastUpdated(t tc.TimeNoMod) { v.LastUpdated = &t }
 func (v *TOCDNFederation) InsertQuery() string           { return insertQuery() }
 func (v *TOCDNFederation) SelectMaxLastUpdatedQuery(where, orderBy, pagination, tableName string) string {
 	return `SELECT max(t) from (
-		SELECT max(federation.last_updated) as t from federation 
-		join federation_deliveryservice fds on fds.federation = federation.id 
-		join deliveryservice ds on ds.id = fds.deliveryservice 
+		SELECT max(federation.last_updated) as t from federation
+		join federation_deliveryservice fds on fds.federation = federation.id
+		join deliveryservice ds on ds.id = fds.deliveryservice
 		join cdn c on c.id = ds.cdn_id ` + where + orderBy + pagination +
 		` UNION ALL
 		select max(last_updated) as t from last_deleted l where l.table_name='federation') as res`
@@ -112,7 +112,7 @@ func (fed *TOCDNFederation) SetKeys(keys map[string]interface{}) {
 }
 
 // Fulfills `Validate' interface
-func (fed *TOCDNFederation) Validate() error {
+func (fed *TOCDNFederation) Validate() (error, error) {
 
 	isDNSName := validation.NewStringRule(govalidator.IsDNSName, "must be a valid hostname")
 	endsWithDot := validation.NewStringRule(
@@ -125,7 +125,7 @@ func (fed *TOCDNFederation) Validate() error {
 		"cname": validation.Validate(fed.CName, validation.Required, endsWithDot, isDNSName),
 		"ttl":   validation.Validate(fed.TTL, validation.Required, validation.Min(0)),
 	}
-	return util.JoinErrs(tovalidate.ToErrors(validateErrs))
+	return util.JoinErrs(tovalidate.ToErrors(validateErrs)), nil
 }
 
 func (fed *TOCDNFederation) CheckIfCDNAndFederationMatch(cdnName string) (error, error, int) {
diff --git a/traffic_ops/traffic_ops_golang/coordinate/coordinates.go b/traffic_ops/traffic_ops_golang/coordinate/coordinates.go
index 51d19fc10a..29b40589b8 100644
--- a/traffic_ops/traffic_ops_golang/coordinate/coordinates.go
+++ b/traffic_ops/traffic_ops_golang/coordinate/coordinates.go
@@ -110,8 +110,8 @@ func IsValidCoordinateName(str string) bool {
 	return i == -1
 }
 
-// Validate fulfills the api.Validator interface
-func (coordinate TOCoordinate) Validate() error {
+// Validate fulfills the api.Validator interface.
+func (coordinate TOCoordinate) Validate() (error, 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"
@@ -120,7 +120,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 util.JoinErrs(tovalidate.ToErrors(errs))
+	return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 }
 
 func (coord *TOCoordinate) Create() (error, error, int) { return api.GenericCreate(coord) }
diff --git a/traffic_ops/traffic_ops_golang/coordinate/coordinates_test.go b/traffic_ops/traffic_ops_golang/coordinate/coordinates_test.go
index 330af9c637..0fa8bb5971 100644
--- a/traffic_ops/traffic_ops_golang/coordinate/coordinates_test.go
+++ b/traffic_ops/traffic_ops_golang/coordinate/coordinates_test.go
@@ -148,7 +148,8 @@ func TestValidate(t *testing.T) {
 		Longitude:   &lo,
 		LastUpdated: &lu,
 	}}
-	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(c.Validate())))
+	err, _ := c.Validate()
+	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(err)))
 
 	expectedErrs := util.JoinErrsStr([]error{
 		errors.New(`'latitude' Must be a floating point number within the range +-90`),
@@ -170,7 +171,7 @@ func TestValidate(t *testing.T) {
 		Longitude:   &lo,
 		LastUpdated: &lu,
 	}}
-	err := c.Validate()
+	err, _ = c.Validate()
 	if err != nil {
 		t.Errorf("expected nil, got %s", err)
 	}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
index 5f045c56d9..3966186eeb 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
@@ -1284,8 +1284,7 @@ func readGetDeliveryServices(h http.Header, params map[string]string, tx *sqlx.T
 		accessibleTo, _ := strconv.Atoi(accessibleTo)
 		accessibleTenants, err := tenant.GetUserTenantIDListTx(tx.Tx, accessibleTo)
 		if err != nil {
-			log.Errorln("unable to get tenants: " + err.Error())
-			return nil, nil, tc.DBError, http.StatusInternalServerError, &maxTime
+			return nil, nil, fmt.Errorf("unable to get tenants: %w", err), http.StatusInternalServerError, &maxTime
 		}
 		where += " AND ds.tenant_id = ANY(CAST(:accessibleTo AS bigint[])) "
 		queryValues["accessibleTo"] = pq.Array(accessibleTenants)
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_required_capabilities.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_required_capabilities.go
index 043495132e..b6b75b3dd8 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_required_capabilities.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_required_capabilities.go
@@ -155,13 +155,13 @@ func (rc *RequiredCapability) GetType() string {
 }
 
 // Validate implements the api.Validator interface.
-func (rc RequiredCapability) Validate() error {
+func (rc RequiredCapability) Validate() (error, error) {
 	errs := validation.Errors{
 		deliveryServiceQueryParam:    validation.Validate(rc.DeliveryServiceID, validation.Required),
 		requiredCapabilityQueryParam: validation.Validate(rc.RequiredCapability, validation.Required),
 	}
 
-	return util.JoinErrs(tovalidate.ToErrors(errs))
+	return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 }
 
 // Update implements the api.CRUDer interface.
@@ -344,8 +344,8 @@ func (rc *RequiredCapability) checkServerCap() (error, error, int) {
 	// Get server capability name
 	name := ""
 	if err := tx.QueryRow(`
-		SELECT name 
-		FROM server_capability 
+		SELECT name
+		FROM server_capability
 		WHERE name = $1`, rc.RequiredCapability).Scan(&name); err != nil && err != sql.ErrNoRows {
 		return nil, fmt.Errorf("querying server capability for name '%v': %v", rc.RequiredCapability, err), http.StatusInternalServerError
 	}
@@ -436,7 +436,7 @@ func (rc *RequiredCapability) ensureDSServerCap() (error, error, int) {
 	dsServerIDs := []int64{}
 	if err := tx.Tx.QueryRow(`
 	SELECT ARRAY(
-		SELECT ds.server 
+		SELECT ds.server
 		FROM deliveryservice_server ds
 		JOIN server s ON ds.server = s.id
 		JOIN type t ON s.type = t.id
@@ -455,7 +455,7 @@ func (rc *RequiredCapability) ensureDSServerCap() (error, error, int) {
 	if err := tx.QueryRow(`
 	SELECT ARRAY(
 		SELECT server
-		FROM server_server_capability 
+		FROM server_server_capability
 		WHERE server = ANY($1)
 		AND server_capability=$2
 	)`, pq.Array(dsServerIDs), rc.RequiredCapability).Scan(pq.Array(&capServerIDs)); err != nil && err != sql.ErrNoRows {
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 b58a027f7e..3c08d1c92d 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/comment/comments.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/comment/comments.go
@@ -31,7 +31,7 @@ import (
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 
-	"github.com/go-ozzo/ozzo-validation"
+	validation "github.com/go-ozzo/ozzo-validation"
 )
 
 //we need a type alias to define functions on
@@ -98,12 +98,12 @@ func (comment TODeliveryServiceRequestComment) GetType() string {
 	return "deliveryservice_request_comment"
 }
 
-func (comment TODeliveryServiceRequestComment) Validate() error {
+func (comment TODeliveryServiceRequestComment) Validate() (error, error) {
 	errs := validation.Errors{
 		"deliveryServiceRequestId": validation.Validate(comment.DeliveryServiceRequestID, validation.NotNil),
 		"value":                    validation.Validate(comment.Value, validation.NotNil),
 	}
-	return util.JoinErrs(tovalidate.ToErrors(errs))
+	return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 }
 
 func (comment *TODeliveryServiceRequestComment) Create() (error, error, int) {
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/request/comment/comments_test.go b/traffic_ops/traffic_ops_golang/deliveryservice/request/comment/comments_test.go
index b16ac6c8bc..de7f41b531 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/comment/comments_test.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/comment/comments_test.go
@@ -69,7 +69,8 @@ func TestInterfaces(t *testing.T) {
 
 func TestValidate(t *testing.T) {
 	c := TODeliveryServiceRequestComment{}
-	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(c.Validate())))
+	err, _ := c.Validate()
+	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(err)))
 
 	expectedErrs := util.JoinErrsStr([]error{
 		errors.New(`'deliveryServiceRequestId' is required`),
@@ -84,7 +85,7 @@ func TestValidate(t *testing.T) {
 	d := 1
 	c = TODeliveryServiceRequestComment{DeliveryServiceRequestCommentNullable: tc.DeliveryServiceRequestCommentNullable{DeliveryServiceRequestID: &d, Value: &v}}
 
-	err := c.Validate()
+	err, _ = c.Validate()
 	if err != nil {
 		t.Errorf("expected nil, got %s", err)
 	}
diff --git a/traffic_ops/traffic_ops_golang/division/divisions.go b/traffic_ops/traffic_ops_golang/division/divisions.go
index 7bcf4b03f0..57d45cc92d 100644
--- a/traffic_ops/traffic_ops_golang/division/divisions.go
+++ b/traffic_ops/traffic_ops_golang/division/divisions.go
@@ -95,11 +95,11 @@ func (division TODivision) GetType() string {
 	return "division"
 }
 
-func (division TODivision) Validate() error {
+func (division TODivision) Validate() (error, error) {
 	errs := validation.Errors{
 		"name": validation.Validate(division.Name, validation.NotNil, validation.Required),
 	}
-	return util.JoinErrs(tovalidate.ToErrors(errs))
+	return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 }
 
 func (dv *TODivision) Create() (error, error, int) { return api.GenericCreate(dv) }
diff --git a/traffic_ops/traffic_ops_golang/division/divisions_test.go b/traffic_ops/traffic_ops_golang/division/divisions_test.go
index 4f7b050fd4..1e683510cc 100644
--- a/traffic_ops/traffic_ops_golang/division/divisions_test.go
+++ b/traffic_ops/traffic_ops_golang/division/divisions_test.go
@@ -111,7 +111,8 @@ func TestInterfaces(t *testing.T) {
 
 func TestValidation(t *testing.T) {
 	div := TODivision{}
-	errs := test.SortErrors(test.SplitErrors(div.Validate()))
+	err, _ := div.Validate()
+	errs := test.SortErrors(test.SplitErrors(err))
 	expected := []error{}
 
 	if reflect.DeepEqual(expected, errs) {
diff --git a/traffic_ops/traffic_ops_golang/origin/origins.go b/traffic_ops/traffic_ops_golang/origin/origins.go
index bfb1523762..de94740dc1 100644
--- a/traffic_ops/traffic_ops_golang/origin/origins.go
+++ b/traffic_ops/traffic_ops_golang/origin/origins.go
@@ -84,7 +84,7 @@ func (origin *TOOrigin) GetType() string {
 	return "origin"
 }
 
-func (origin *TOOrigin) Validate() error {
+func (origin *TOOrigin) Validate() (error, error) {
 
 	noSpaces := validation.NewStringRule(tovalidate.NoSpaces, "cannot contain spaces")
 	validProtocol := validation.NewStringRule(tovalidate.IsOneOfStringICase("http", "https"), "must be http or https")
@@ -103,7 +103,7 @@ func (origin *TOOrigin) Validate() error {
 		"protocol":          validation.Validate(origin.Protocol, validation.Required, validProtocol),
 		"tenantId":          validation.Validate(origin.TenantID, validation.Min(1)),
 	}
-	return util.JoinErrs(tovalidate.ToErrors(validateErrs))
+	return util.JoinErrs(tovalidate.ToErrors(validateErrs)), nil
 }
 
 // GetTenantID returns a pointer to the Origin's tenant ID from the Tx, whether or not the Origin exists, and any error encountered
@@ -182,8 +182,7 @@ func getOrigins(h http.Header, params map[string]string, tx *sqlx.Tx, user *auth
 
 	tenantIDs, err := tenant.GetUserTenantIDListTx(tx.Tx, user.TenantID)
 	if err != nil {
-		log.Errorln("received error querying for user's tenants: " + err.Error())
-		return nil, nil, tc.DBError, http.StatusInternalServerError, nil
+		return nil, nil, fmt.Errorf("received error querying for user's tenants: %w", err), http.StatusInternalServerError, nil
 	}
 	where, queryValues = dbhelpers.AddTenancyCheck(where, queryValues, "o.tenant", tenantIDs)
 
diff --git a/traffic_ops/traffic_ops_golang/origin/origins_test.go b/traffic_ops/traffic_ops_golang/origin/origins_test.go
index f072527ca9..a4e1220ab8 100644
--- a/traffic_ops/traffic_ops_golang/origin/origins_test.go
+++ b/traffic_ops/traffic_ops_golang/origin/origins_test.go
@@ -186,7 +186,8 @@ func TestValidate(t *testing.T) {
 		FQDN:              nil,
 		Protocol:          nil,
 	}}
-	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(c.Validate())))
+	err, _ := c.Validate()
+	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(err)))
 
 	expectedErrs := util.JoinErrsStr([]error{
 		errors.New(`'deliveryServiceId' is required`),
@@ -218,7 +219,7 @@ func TestValidate(t *testing.T) {
 		Protocol:          &pro,
 		LastUpdated:       &lu,
 	}}
-	err := c.Validate()
+	err, _ = c.Validate()
 	if err != nil {
 		t.Errorf("expected nil, got %s", err)
 	}
@@ -323,7 +324,8 @@ func TestValidate(t *testing.T) {
 				c.IP6Address = &tc.Str
 				value = tc.Str
 			}
-			errStr := util.JoinErrsStr(test.SortErrors(test.SplitErrors(c.Validate())))
+			err, _ = c.Validate()
+			errStr := util.JoinErrsStr(test.SortErrors(test.SplitErrors(err)))
 			if !reflect.DeepEqual(util.JoinErrsStr(tc.ExpectedErrors), errStr) {
 				t.Errorf("given: '%v', expected %s, got %s", value, tc.ExpectedErrors, errStr)
 			}
diff --git a/traffic_ops/traffic_ops_golang/parameter/parameters.go b/traffic_ops/traffic_ops_golang/parameter/parameters.go
index d1ccc4e3e8..8d719fa3d3 100644
--- a/traffic_ops/traffic_ops_golang/parameter/parameters.go
+++ b/traffic_ops/traffic_ops_golang/parameter/parameters.go
@@ -119,7 +119,7 @@ func (param *TOParameter) GetType() string {
 }
 
 // Validate fulfills the api.Validator interface
-func (param TOParameter) Validate() error {
+func (param TOParameter) Validate() (error, error) {
 	// Test
 	// - Secure Flag is always set to either 1/0
 	// - Admin rights only
@@ -133,7 +133,7 @@ func (param TOParameter) Validate() error {
 		errs[atscfg.ParentConfigFileName+" "+atscfg.ParentConfigCacheParamWeight] = validation.Validate(*param.Value, tovalidate.StringIsValidFloat())
 	}
 
-	return util.JoinErrs(tovalidate.ToErrors(errs))
+	return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 }
 
 func (pa *TOParameter) Create() (error, error, int) {
diff --git a/traffic_ops/traffic_ops_golang/physlocation/phys_locations.go b/traffic_ops/traffic_ops_golang/physlocation/phys_locations.go
index d6fbd85907..51b31c455e 100644
--- a/traffic_ops/traffic_ops_golang/physlocation/phys_locations.go
+++ b/traffic_ops/traffic_ops_golang/physlocation/phys_locations.go
@@ -90,7 +90,7 @@ func (pl *TOPhysLocation) GetType() string {
 	return "physLocation"
 }
 
-func (pl *TOPhysLocation) Validate() error {
+func (pl *TOPhysLocation) Validate() (error, error) {
 	errs := validation.Errors{
 		"address":   validation.Validate(pl.Address, validation.Required),
 		"city":      validation.Validate(pl.City, validation.Required),
@@ -101,9 +101,9 @@ func (pl *TOPhysLocation) Validate() error {
 		"zip":       validation.Validate(pl.Zip, validation.Required),
 	}
 	if errs != nil {
-		return util.JoinErrs(tovalidate.ToErrors(errs))
+		return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 	}
-	return nil
+	return nil, nil
 }
 
 func (pl *TOPhysLocation) Read(h http.Header, useIMS bool) ([]interface{}, error, error, int, *time.Time) {
diff --git a/traffic_ops/traffic_ops_golang/physlocation/phys_locations_test.go b/traffic_ops/traffic_ops_golang/physlocation/phys_locations_test.go
index 67d0eb99cd..727183f453 100644
--- a/traffic_ops/traffic_ops_golang/physlocation/phys_locations_test.go
+++ b/traffic_ops/traffic_ops_golang/physlocation/phys_locations_test.go
@@ -134,8 +134,8 @@ func TestInterfaces(t *testing.T) {
 }
 
 func TestValidate(t *testing.T) {
-	p := TOPhysLocation{}
-	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(p.Validate())))
+	err, _ := (&TOPhysLocation{}).Validate()
+	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(err)))
 	expected := util.JoinErrsStr(test.SortErrors([]error{
 		errors.New("'state' cannot be blank"),
 		errors.New("'zip' cannot be blank"),
diff --git a/traffic_ops/traffic_ops_golang/profile/profiles.go b/traffic_ops/traffic_ops_golang/profile/profiles.go
index 4ae0e8ae48..2305e8b8af 100644
--- a/traffic_ops/traffic_ops_golang/profile/profiles.go
+++ b/traffic_ops/traffic_ops_golang/profile/profiles.go
@@ -102,7 +102,7 @@ func (prof *TOProfile) GetType() string {
 	return "profile"
 }
 
-func (prof *TOProfile) Validate() error {
+func (prof *TOProfile) Validate() (error, error) {
 	errs := validation.Errors{
 		NameQueryParam: validation.Validate(prof.Name, validation.By(
 			func(value interface{}) error {
@@ -124,9 +124,9 @@ func (prof *TOProfile) Validate() error {
 		TypeQueryParam:        validation.Validate(prof.Type, validation.Required),
 	}
 	if errs != nil {
-		return util.JoinErrs(tovalidate.ToErrors(errs))
+		return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 	}
-	return nil
+	return nil, nil
 }
 
 func (prof *TOProfile) Read(h http.Header, useIMS bool) ([]interface{}, error, error, int, *time.Time) {
diff --git a/traffic_ops/traffic_ops_golang/profile/profiles_test.go b/traffic_ops/traffic_ops_golang/profile/profiles_test.go
index 26a98dc4c9..ebd6e86944 100644
--- a/traffic_ops/traffic_ops_golang/profile/profiles_test.go
+++ b/traffic_ops/traffic_ops_golang/profile/profiles_test.go
@@ -138,7 +138,8 @@ func TestInterfaces(t *testing.T) {
 
 func TestValidate(t *testing.T) {
 	p := TOProfile{}
-	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(p.Validate())))
+	err, _ := p.Validate()
+	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(err)))
 	expected := util.JoinErrsStr(test.SortErrors([]error{
 		errors.New("'cdn' cannot be blank"),
 		errors.New("'description' cannot be blank"),
@@ -159,7 +160,7 @@ func TestValidate(t *testing.T) {
 	p.Type = new(string)
 	*p.Type = "type"
 
-	err := p.Validate()
+	err, _ = p.Validate()
 	if !strings.Contains(err.Error(), "cannot contain spaces") {
 		t.Error("Expected an error about the Profile name containing spaces")
 	}
diff --git a/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go b/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go
index 0065660309..b66e4379dd 100644
--- a/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go
+++ b/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go
@@ -98,15 +98,15 @@ func (pp *TOProfileParameter) SetKeys(keys map[string]interface{}) {
 	pp.ParameterID = &paramId
 }
 
-// Validate fulfills the api.Validator interface
-func (pp *TOProfileParameter) Validate() error {
+// Validate fulfills the api.Validator interface.
+func (pp *TOProfileParameter) Validate() (error, error) {
 
 	errs := validation.Errors{
 		"profileId":   validation.Validate(pp.ProfileID, validation.Required),
 		"parameterId": validation.Validate(pp.ParameterID, validation.Required),
 	}
 
-	return util.JoinErrs(tovalidate.ToErrors(errs))
+	return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 }
 
 //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 27a180722e..9263059fbd 100644
--- a/traffic_ops/traffic_ops_golang/region/regions.go
+++ b/traffic_ops/traffic_ops_golang/region/regions.go
@@ -94,14 +94,14 @@ func (region *TORegion) GetType() string {
 	return "region"
 }
 
-func (region *TORegion) Validate() error {
+func (region *TORegion) Validate() (error, error) {
 	if len(region.Name) < 1 {
-		return errors.New(`region 'name' is required`)
+		return errors.New(`region 'name' is required`), nil
 	}
 	if region.Division == 0 {
-		return errors.New(`region 'division' is required`)
+		return errors.New(`region 'division' is required`), nil
 	}
-	return nil
+	return nil, nil
 }
 
 func (rg *TORegion) Read(h http.Header, useIMS bool) ([]interface{}, error, error, int, *time.Time) {
diff --git a/traffic_ops/traffic_ops_golang/region/regions_test.go b/traffic_ops/traffic_ops_golang/region/regions_test.go
index 27ea29f4f6..d34d337e2d 100644
--- a/traffic_ops/traffic_ops_golang/region/regions_test.go
+++ b/traffic_ops/traffic_ops_golang/region/regions_test.go
@@ -118,7 +118,8 @@ func TestValidation(t *testing.T) {
 		LastUpdated:  tc.TimeNoMod{Time: time.Now()},
 	}
 	testTORegion := TORegion{Region: testRegion}
-	errs := test.SortErrors(test.SplitErrors(testTORegion.Validate()))
+	err, _ := testTORegion.Validate()
+	errs := test.SortErrors(test.SplitErrors(err))
 
 	if len(errs) > 0 {
 		t.Errorf(`expected no errors,  got %v`, errs)
@@ -130,7 +131,8 @@ func TestValidation(t *testing.T) {
 		LastUpdated: tc.TimeNoMod{Time: time.Now()},
 	}
 	testTORegionNoDivision := TORegion{Region: testRegionNoDivision}
-	errs = test.SortErrors(test.SplitErrors(testTORegionNoDivision.Validate()))
+	err, _ = testTORegionNoDivision.Validate()
+	errs = test.SortErrors(test.SplitErrors(err))
 	if len(errs) == 0 {
 		t.Errorf(`expected an error with a nil division id, received no error`)
 	} else {
diff --git a/traffic_ops/traffic_ops_golang/role/roles.go b/traffic_ops/traffic_ops_golang/role/roles.go
index 623958eb1a..43878e53b2 100644
--- a/traffic_ops/traffic_ops_golang/role/roles.go
+++ b/traffic_ops/traffic_ops_golang/role/roles.go
@@ -129,7 +129,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, error) {
 	errs := validation.Errors{
 		"name":        validation.Validate(role.Name, validation.Required),
 		"description": validation.Validate(role.Description, validation.Required),
@@ -141,14 +141,13 @@ func (role TORole) Validate() error {
 	if role.ReqInfo.Tx != nil {
 		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 tc.DBError
+			return nil, fmt.Errorf("got error from selecting bad capabilities: %w", err)
 		}
 		if len(badCaps) > 0 {
 			errsToReturn = append(errsToReturn, fmt.Errorf("can not add non-existent capabilities: %v", badCaps))
 		}
 	}
-	return util.JoinErrs(errsToReturn)
+	return util.JoinErrs(errsToReturn), nil
 }
 
 func (role *TORole) Create() (error, error, int) {
diff --git a/traffic_ops/traffic_ops_golang/role/roles_test.go b/traffic_ops/traffic_ops_golang/role/roles_test.go
index bc6b508c6a..4d65a421cf 100644
--- a/traffic_ops/traffic_ops_golang/role/roles_test.go
+++ b/traffic_ops/traffic_ops_golang/role/roles_test.go
@@ -86,7 +86,8 @@ func TestValidate(t *testing.T) {
 		APIInfoImpl: api.APIInfoImpl{ReqInfo: &reqInfo},
 		Role:        role,
 	}
-	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(r.Validate())))
+	userErr, _ := r.Validate()
+	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(userErr)))
 
 	expectedErrs := util.JoinErrsStr([]error{
 		errors.New(`'description' cannot be blank`),
@@ -106,9 +107,12 @@ func TestValidate(t *testing.T) {
 		APIInfoImpl: api.APIInfoImpl{ReqInfo: &reqInfo},
 		Role:        role,
 	}
-	err := r.Validate()
-	if err != nil {
-		t.Errorf("expected nil, got %s", err)
+	userErr, sysErr := r.Validate()
+	if userErr != nil {
+		t.Errorf("expected nil user error, got: %s", userErr)
+	}
+	if sysErr != nil {
+		t.Errorf("expected nil system error, got: %v", sysErr)
 	}
 
 }
diff --git a/traffic_ops/traffic_ops_golang/server/servers.go b/traffic_ops/traffic_ops_golang/server/servers.go
index ffe5f41183..205fe98077 100644
--- a/traffic_ops/traffic_ops_golang/server/servers.go
+++ b/traffic_ops/traffic_ops_golang/server/servers.go
@@ -402,7 +402,7 @@ func newUUID() *string {
 	return &uuidReference
 }
 
-func validateCommon(s *tc.CommonServerProperties, tx *sql.Tx) []error {
+func validateCommon(s *tc.CommonServerProperties, tx *sql.Tx) ([]error, error) {
 
 	noSpaces := validation.NewStringRule(tovalidate.NoSpaces, "cannot contain spaces")
 
@@ -420,7 +420,7 @@ func validateCommon(s *tc.CommonServerProperties, tx *sql.Tx) []error {
 	})
 
 	if len(errs) > 0 {
-		return errs
+		return errs, nil
 	}
 
 	if _, err := tc.ValidateTypeID(tx, s.TypeID, "server"); err != nil {
@@ -429,20 +429,18 @@ func validateCommon(s *tc.CommonServerProperties, tx *sql.Tx) []error {
 
 	var cdnID int
 	if err := tx.QueryRow("SELECT cdn from profile WHERE id=$1", s.ProfileID).Scan(&cdnID); err != nil {
-		log.Errorf("could not execute select cdnID from profile: %s\n", err)
-		if err == sql.ErrNoRows {
-			errs = append(errs, fmt.Errorf("no such profileId: '%d'", *s.ProfileID))
-		} else {
-			errs = append(errs, tc.DBError)
+		if errors.Is(err, sql.ErrNoRows) {
+			errs = append(errs, fmt.Errorf("no such Profile: #%d", *s.ProfileID))
+			return errs, nil
 		}
-		return errs
+		return nil, fmt.Errorf("could not execute select cdnID from profile: %w", err)
 	}
 
 	log.Infof("got cdn id: %d from profile and cdn id: %d from server", cdnID, *s.CDNID)
 	if cdnID != *s.CDNID {
 		errs = append(errs, fmt.Errorf("CDN id '%d' for profile '%d' does not match Server CDN '%d'", cdnID, *s.ProfileID, *s.CDNID))
 	}
-	return errs
+	return errs, nil
 }
 
 func validateCommonV40(s *tc.ServerV40, tx *sql.Tx) ([]error, error) {
@@ -495,7 +493,7 @@ func validateCommonV40(s *tc.ServerV40, tx *sql.Tx) ([]error, error) {
 	return errs, nil
 }
 
-func validateV1(s *tc.ServerNullableV11, tx *sql.Tx) error {
+func validateV1(s *tc.ServerNullableV11, tx *sql.Tx) (error, error) {
 	if s.IP6Address != nil && len(strings.TrimSpace(*s.IP6Address)) == 0 {
 		s.IP6Address = nil
 	}
@@ -520,16 +518,17 @@ func validateV1(s *tc.ServerNullableV11, tx *sql.Tx) error {
 	}
 
 	errs = append(errs, tovalidate.ToErrors(validateErrs)...)
-	errs = append(errs, validateCommon(&s.CommonServerProperties, tx)...)
+	commonErrs, sysErr := validateCommon(&s.CommonServerProperties, tx)
+	errs = append(errs, commonErrs...)
 
-	return util.JoinErrs(errs)
+	return util.JoinErrs(errs), sysErr
 }
 
-func validateV2(s *tc.ServerNullableV2, tx *sql.Tx) error {
+func validateV2(s *tc.ServerNullableV2, tx *sql.Tx) (error, error) {
 	var errs []error
 
-	if err := validateV1(&s.ServerNullableV11, tx); err != nil {
-		return err
+	if userErr, sysErr := validateV1(&s.ServerNullableV11, tx); userErr != nil || sysErr != nil {
+		return userErr, sysErr
 	}
 
 	// default boolean value is false
@@ -551,7 +550,7 @@ func validateV2(s *tc.ServerNullableV2, tx *sql.Tx) error {
 	if *s.IP6IsService && (s.IP6Address == nil || *s.IP6Address == "") {
 		errs = append(errs, tc.EmptyAddressCannotBeAServiceAddressError)
 	}
-	return util.JoinErrs(errs)
+	return util.JoinErrs(errs), nil
 }
 
 func validateMTU(mtu interface{}) error {
@@ -674,10 +673,10 @@ WHERE (profiles = $1::text[])
 	return serviceInterface, util.JoinErrs(errs), nil
 }
 
-func validateV3(s *tc.ServerV30, tx *sql.Tx) (string, error) {
+func validateV3(s *tc.ServerV30, tx *sql.Tx) (string, error, error) {
 
 	if len(s.Interfaces) == 0 {
-		return "", errors.New("a server must have at least one interface")
+		return "", errors.New("a server must have at least one interface"), nil
 	}
 	var errs []error
 	var serviceAddrV4Found bool
@@ -740,8 +739,10 @@ func validateV3(s *tc.ServerV30, tx *sql.Tx) (string, error) {
 		errs = append(errs, errors.New("a server must have at least one service address"))
 	}
 
-	if errs = append(errs, validateCommon(&s.CommonServerProperties, tx)...); errs != nil {
-		return serviceInterface, util.JoinErrs(errs)
+	commonErrs, sysErr := validateCommon(&s.CommonServerProperties, tx)
+	errs = append(errs, commonErrs...)
+	if len(errs) > 0 || sysErr != nil {
+		return serviceInterface, util.JoinErrs(errs), sysErr
 	}
 	query := `
 SELECT s.ID, ip.address FROM server s
@@ -760,7 +761,7 @@ and p.id = $1
 		rows, err = tx.Query(query, *s.ProfileID)
 	}
 	if err != nil {
-		errs = append(errs, errors.New("unable to determine service address uniqueness"))
+		return serviceInterface, util.JoinErrs(errs), fmt.Errorf("unable to determine service address uniqueness: querying: %w", err)
 	} else if rows != nil {
 		defer rows.Close()
 		for rows.Next() {
@@ -768,14 +769,14 @@ and p.id = $1
 			var ipaddress string
 			err = rows.Scan(&id, &ipaddress)
 			if err != nil {
-				errs = append(errs, errors.New("unable to determine service address uniqueness"))
+				return serviceInterface, util.JoinErrs(errs), fmt.Errorf("unable to determine service address uniqueness: scanning: %w", err)
 			} else if (ipaddress == ipv4 || ipaddress == ipv6) && (s.ID == nil || *s.ID != id) {
 				errs = append(errs, fmt.Errorf("there exists a server with id %v on the same profile that has the same service address %s", id, ipaddress))
 			}
 		}
 	}
 
-	return serviceInterface, util.JoinErrs(errs)
+	return serviceInterface, util.JoinErrs(errs), nil
 }
 
 // Read is the handler for GET requests to /servers.
@@ -1515,9 +1516,13 @@ func Update(w http.ResponseWriter, r *http.Request) {
 			serverV3.StatusLastUpdated = original.StatusLastUpdated
 			statusLastUpdatedTime = *original.StatusLastUpdated
 		}
-		_, err := validateV3(&serverV3, tx)
-		if err != nil {
-			api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+		_, userErr, sysErr := validateV3(&serverV3, tx)
+		if userErr != nil || sysErr != nil {
+			code := http.StatusBadRequest
+			if sysErr != nil {
+				code = http.StatusInternalServerError
+			}
+			api.HandleErr(w, r, tx, code, userErr, sysErr)
 			return
 		}
 		profileName, err := dbhelpers.UpdateServerProfileTableForV2V3(serverV3.ID, serverV3.ProfileID, (original.ProfileNames)[0], tx)
@@ -1542,9 +1547,13 @@ func Update(w http.ResponseWriter, r *http.Request) {
 			api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
 			return
 		}
-		err := validateV2(&legacyServer, tx)
-		if err != nil {
-			api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+		userErr, sysErr := validateV2(&legacyServer, tx)
+		if userErr != nil || sysErr != nil {
+			code := http.StatusBadRequest
+			if sysErr != nil {
+				code = http.StatusInternalServerError
+			}
+			api.HandleErr(w, r, tx, code, userErr, sysErr)
 			return
 		}
 		profileName, err := dbhelpers.UpdateServerProfileTableForV2V3(legacyServer.ID, legacyServer.ProfileID, (original.ProfileNames)[0], tx)
@@ -1811,8 +1820,8 @@ func insertServerProfile(id int, pName []string, tx *sql.Tx) (error, error, int)
 	}
 	insertQuery := `
 	INSERT INTO server_profile (
-		server, 
-		profile_name, 
+		server,
+		profile_name,
 		priority
 	)SELECT $1, profile_name, priority
 	FROM UNNEST($2::text[], $3::int[]) WITH ORDINALITY AS tmp(profile_name, priority)
@@ -1847,8 +1856,12 @@ func createV2(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
 
 	server.XMPPID = newUUID()
 
-	if err := validateV2(&server, inf.Tx.Tx); err != nil {
-		api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, err, nil)
+	if userErr, sysErr := validateV2(&server, inf.Tx.Tx); userErr != nil || sysErr != nil {
+		code := http.StatusBadRequest
+		if sysErr != nil {
+			code = http.StatusInternalServerError
+		}
+		api.HandleErr(w, r, inf.Tx.Tx, code, userErr, sysErr)
 		return
 	}
 
@@ -1984,9 +1997,13 @@ func createV3(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
 
 	server.XMPPID = newUUID()
 
-	_, err := validateV3(&server, inf.Tx.Tx)
-	if err != nil {
-		api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, err, nil)
+	_, userErr, sysErr := validateV3(&server, inf.Tx.Tx)
+	if userErr != nil || sysErr != nil {
+		code := http.StatusBadRequest
+		if sysErr != nil {
+			code = http.StatusInternalServerError
+		}
+		api.HandleErr(w, r, inf.Tx.Tx, code, userErr, sysErr)
 		return
 	}
 
diff --git a/traffic_ops/traffic_ops_golang/server/servers_assignment.go b/traffic_ops/traffic_ops_golang/server/servers_assignment.go
index d9f0d87ac3..0107a9db89 100644
--- a/traffic_ops/traffic_ops_golang/server/servers_assignment.go
+++ b/traffic_ops/traffic_ops_golang/server/servers_assignment.go
@@ -432,8 +432,7 @@ INSERT INTO parameter (config_file, name, value)
 	for rows.Next() {
 		var ID int64
 		if err := rows.Scan(&ID); err != nil {
-			log.Errorf("could not scan parameter ID: %s\n", err)
-			return nil, tc.DBError
+			return nil, fmt.Errorf("could not scan parameter ID: %w", err)
 		}
 		parameterIds = append(parameterIds, ID)
 	}
diff --git a/traffic_ops/traffic_ops_golang/server/servers_server_capability.go b/traffic_ops/traffic_ops_golang/server/servers_server_capability.go
index ef56814e28..64291f21cd 100644
--- a/traffic_ops/traffic_ops_golang/server/servers_server_capability.go
+++ b/traffic_ops/traffic_ops_golang/server/servers_server_capability.go
@@ -112,14 +112,14 @@ func (ssc *TOServerServerCapability) GetType() string {
 	return "server server_capability"
 }
 
-// Validate fulfills the api.Validator interface
-func (ssc TOServerServerCapability) Validate() error {
+// Validate fulfills the api.Validator interface.
+func (ssc TOServerServerCapability) Validate() (error, error) {
 	errs := validation.Errors{
 		ServerQueryParam:           validation.Validate(ssc.ServerID, validation.Required),
 		ServerCapabilityQueryParam: validation.Validate(ssc.ServerCapability, validation.Required),
 	}
 
-	return util.JoinErrs(tovalidate.ToErrors(errs))
+	return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 }
 
 func (ssc *TOServerServerCapability) Read(h http.Header, useIMS bool) ([]interface{}, error, error, int, *time.Time) {
@@ -375,7 +375,7 @@ SELECT ARRAY(
 	SELECT dsrc.deliveryservice_id
 	FROM deliveryservices_required_capability as dsrc
 	WHERE deliveryservice_id IN (
-		SELECT deliveryservice 
+		SELECT deliveryservice
 		FROM deliveryservice_server
 		WHERE server = $1)
 	AND dsrc.required_capability = $2)`
diff --git a/traffic_ops/traffic_ops_golang/server/servers_test.go b/traffic_ops/traffic_ops_golang/server/servers_test.go
index 94b33aa2a6..8283284143 100644
--- a/traffic_ops/traffic_ops_golang/server/servers_test.go
+++ b/traffic_ops/traffic_ops_golang/server/servers_test.go
@@ -575,7 +575,7 @@ func TestV3Validations(t *testing.T) {
 
 	tx := db.MustBegin().Tx
 
-	_, err = validateV3(&testServer, tx)
+	_, err, _ = validateV3(&testServer, tx)
 	if err != nil {
 		t.Errorf("Unexpected error validating test server: %v", err)
 	}
@@ -587,7 +587,7 @@ func TestV3Validations(t *testing.T) {
 	mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows)
 	mock.ExpectQuery("SELECT").WillReturnRows(cdnRows)
 
-	_, err = validateV3(&testServer, tx)
+	_, err, _ = validateV3(&testServer, tx)
 	if err == nil {
 		t.Errorf("Expected a server with no interfaces to be invalid")
 	} else {
@@ -601,7 +601,7 @@ func TestV3Validations(t *testing.T) {
 	mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows)
 	mock.ExpectQuery("SELECT").WillReturnRows(cdnRows)
 
-	_, err = validateV3(&testServer, tx)
+	_, err, _ = validateV3(&testServer, tx)
 	if err == nil {
 		t.Errorf("Expected a server with nil interfaces to be invalid")
 	} else {
@@ -618,7 +618,7 @@ func TestV3Validations(t *testing.T) {
 	mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows)
 	mock.ExpectQuery("SELECT").WillReturnRows(cdnRows)
 
-	_, err = validateV3(&testServer, tx)
+	_, err, _ = validateV3(&testServer, tx)
 	if err == nil {
 		t.Errorf("Expected a server an MTU < 1280 to be invalid")
 	} else {
@@ -634,7 +634,7 @@ func TestV3Validations(t *testing.T) {
 	mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows)
 	mock.ExpectQuery("SELECT").WillReturnRows(cdnRows)
 
-	_, err = validateV3(&testServer, tx)
+	_, err, _ = validateV3(&testServer, tx)
 	if err == nil {
 		t.Errorf("Expected a server with no IP addresses to be invalid")
 	} else {
@@ -649,7 +649,7 @@ func TestV3Validations(t *testing.T) {
 	mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows)
 	mock.ExpectQuery("SELECT").WillReturnRows(cdnRows)
 
-	_, err = validateV3(&testServer, tx)
+	_, err, _ = validateV3(&testServer, tx)
 	if err == nil {
 		t.Errorf("Expected a server with nil IP addresses to be invalid")
 	} else {
@@ -670,7 +670,7 @@ func TestV3Validations(t *testing.T) {
 	mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows)
 	mock.ExpectQuery("SELECT").WillReturnRows(cdnRows)
 
-	_, err = validateV3(&testServer, tx)
+	_, err, _ = validateV3(&testServer, tx)
 	if err == nil {
 		t.Errorf("Expected a server with no service addresses to be invalid")
 	} else {
@@ -684,7 +684,7 @@ func TestV3Validations(t *testing.T) {
 	mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows)
 	mock.ExpectQuery("SELECT").WillReturnRows(cdnRows)
 
-	_, err = validateV3(&testServer, tx)
+	_, err, _ = validateV3(&testServer, tx)
 	if err == nil {
 		t.Errorf("Expected a server with too many interfaces with service addresses to be invalid")
 	} else {
@@ -704,7 +704,7 @@ func TestV3Validations(t *testing.T) {
 	mock.ExpectQuery("SELECT name, use_in_table").WillReturnRows(typeRows)
 	mock.ExpectQuery("SELECT").WillReturnRows(cdnRows)
 
-	_, err = validateV3(&testServer, tx)
+	_, err, _ = validateV3(&testServer, tx)
 	if err == nil {
 		t.Errorf("Expected a server with no service addresses to be invalid")
 	} else {
diff --git a/traffic_ops/traffic_ops_golang/server/servers_update_status.go b/traffic_ops/traffic_ops_golang/server/servers_update_status.go
index b395547575..bdc2e9aa0d 100644
--- a/traffic_ops/traffic_ops_golang/server/servers_update_status.go
+++ b/traffic_ops/traffic_ops_golang/server/servers_update_status.go
@@ -21,6 +21,7 @@ package server
 
 import (
 	"database/sql"
+	"fmt"
 	"net/http"
 
 	"github.com/lib/pq"
@@ -39,7 +40,7 @@ func GetServerUpdateStatusHandler(w http.ResponseWriter, r *http.Request) {
 	}
 	defer inf.Close()
 
-	serverUpdateStatuses, err := getServerUpdateStatus(inf.Tx.Tx, inf.Config, inf.Params["host_name"])
+	serverUpdateStatuses, err, _ := getServerUpdateStatus(inf.Tx.Tx, inf.Config, inf.Params["host_name"])
 	if err != nil {
 		api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, err)
 		return
@@ -55,7 +56,7 @@ func GetServerUpdateStatusHandler(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-func getServerUpdateStatus(tx *sql.Tx, cfg *config.Config, hostName string) ([]tc.ServerUpdateStatusV40, error) {
+func getServerUpdateStatus(tx *sql.Tx, cfg *config.Config, hostName string) ([]tc.ServerUpdateStatusV40, error, error) {
 
 	updateStatuses := []tc.ServerUpdateStatusV40{}
 
@@ -86,7 +87,7 @@ UNION ALL
  * ancestor topology node found by topology_ancestors.
  */
 ), server_topology_ancestors AS (
-SELECT s.id, 
+SELECT s.id,
 	s.cachegroup,
 	s.cdn_id,
 	s.config_update_time > s.config_apply_time AS upd_pending,
@@ -99,7 +100,7 @@ SELECT s.id,
 	JOIN status ON status.id = s.status
 	WHERE status.name = ANY($1::TEXT[])
 ), parentservers AS (
-SELECT ps.id, 
+SELECT ps.id,
 	ps.cachegroup,
 	ps.cdn_id,
 	ps.config_update_time > ps.config_apply_time AS upd_pending,
@@ -160,7 +161,7 @@ ORDER BY s.id
 	rows, err := tx.Query(selectQuery, pq.Array(cacheStatusesToCheck), tc.UseRevalPendingParameterName, tc.GlobalConfigFileName, pq.Array(cacheGroupTypes), hostName)
 	if err != nil {
 		log.Errorf("could not execute query: %s\n", err)
-		return nil, tc.DBError
+		return nil, nil, fmt.Errorf("could not execute query: %w", err)
 	}
 	defer log.Close(rows, "getServerUpdateStatus(): unable to close db connection")
 
@@ -168,10 +169,9 @@ ORDER BY s.id
 		var us tc.ServerUpdateStatusV40
 		var serverType string
 		if err := rows.Scan(&us.HostId, &us.HostName, &serverType, &us.RevalPending, &us.UseRevalPending, &us.UpdatePending, &us.Status, &us.ParentPending, &us.ParentRevalPending, &us.ConfigUpdateTime, &us.ConfigApplyTime, &us.RevalidateUpdateTime, &us.RevalidateApplyTime); err != nil {
-			log.Errorf("could not scan server update status: %s\n", err)
-			return nil, tc.DBError
+			return nil, nil, fmt.Errorf("could not scan server update status: %w", err)
 		}
 		updateStatuses = append(updateStatuses, us)
 	}
-	return updateStatuses, nil
+	return updateStatuses, nil, nil
 }
diff --git a/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go b/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go
index 6f20fc6c2e..bdcbf4e91a 100644
--- a/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go
+++ b/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go
@@ -58,7 +58,7 @@ func TestGetServerUpdateStatus(t *testing.T) {
 	}
 	defer tx.Commit()
 
-	result, err := getServerUpdateStatus(tx, &config.Config{ConfigTrafficOpsGolang: config.ConfigTrafficOpsGolang{DBQueryTimeoutSeconds: 20}}, "host_name_1")
+	result, err, _ := getServerUpdateStatus(tx, &config.Config{ConfigTrafficOpsGolang: config.ConfigTrafficOpsGolang{DBQueryTimeoutSeconds: 20}}, "host_name_1")
 	if err != nil {
 		t.Errorf("getServerUpdateStatus: %v", err)
 	}
diff --git a/traffic_ops/traffic_ops_golang/servercapability/servercapability.go b/traffic_ops/traffic_ops_golang/servercapability/servercapability.go
index 383c1cfaff..daffb54387 100644
--- a/traffic_ops/traffic_ops_golang/servercapability/servercapability.go
+++ b/traffic_ops/traffic_ops_golang/servercapability/servercapability.go
@@ -107,12 +107,12 @@ func (v *TOServerCapability) GetType() string {
 	return "server capability"
 }
 
-func (v *TOServerCapability) Validate() error {
+func (v *TOServerCapability) Validate() (error, error) {
 	rule := validation.NewStringRule(tovalidate.IsAlphanumericUnderscoreDash, "must consist of only alphanumeric, dash, or underscore characters")
 	errs := validation.Errors{
 		"name": validation.Validate(v.Name, validation.Required, rule),
 	}
-	return util.JoinErrs(tovalidate.ToErrors(errs))
+	return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 }
 
 func (v *TOServerCapability) Read(h http.Header, useIMS bool) ([]interface{}, error, error, int, *time.Time) {
diff --git a/traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go b/traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go
index 893eaf978f..f3e46e96f5 100644
--- a/traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go
+++ b/traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go
@@ -31,7 +31,7 @@ import (
 	"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"
-	"github.com/go-ozzo/ozzo-validation"
+	validation "github.com/go-ozzo/ozzo-validation"
 )
 
 type TOServiceCategory struct {
@@ -91,12 +91,12 @@ func (serviceCategory *TOServiceCategory) SelectMaxLastUpdatedQuery(where, order
 	select max(last_updated) as t from last_deleted l where l.table_name='service_category') as res`
 }
 
-func (serviceCategory TOServiceCategory) Validate() error {
+func (serviceCategory TOServiceCategory) Validate() (error, error) {
 	nameRule := validation.NewStringRule(tovalidate.IsAlphanumericDash, "must consist of only alphanumeric or dash characters.")
 	errs := validation.Errors{
 		"name": validation.Validate(serviceCategory.Name, validation.Required, nameRule),
 	}
-	return util.JoinErrs(tovalidate.ToErrors(errs))
+	return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 }
 
 func (serviceCategory *TOServiceCategory) Create() (error, error, int) {
@@ -129,8 +129,12 @@ func Update(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if err := newSC.Validate(); err != nil {
-		api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, err, nil)
+	if userErr, sysErr := newSC.Validate(); userErr != nil || sysErr != nil {
+		code := http.StatusBadRequest
+		if sysErr != nil {
+			code = http.StatusInternalServerError
+		}
+		api.HandleErr(w, r, inf.Tx.Tx, code, userErr, sysErr)
 		return
 	}
 
diff --git a/traffic_ops/traffic_ops_golang/staticdnsentry/staticdnsentry.go b/traffic_ops/traffic_ops_golang/staticdnsentry/staticdnsentry.go
index 5030dc4e83..298964913f 100644
--- a/traffic_ops/traffic_ops_golang/staticdnsentry/staticdnsentry.go
+++ b/traffic_ops/traffic_ops_golang/staticdnsentry/staticdnsentry.go
@@ -94,11 +94,11 @@ func (staticDNSEntry *TOStaticDNSEntry) SetKeys(keys map[string]interface{}) {
 	staticDNSEntry.ID = &i
 }
 
-// Validate fulfills the api.Validator interface
-func (staticDNSEntry TOStaticDNSEntry) Validate() error {
+// Validate fulfills the api.Validator interface.
+func (staticDNSEntry TOStaticDNSEntry) Validate() (error, error) {
 	typeStr, err := tc.ValidateTypeID(staticDNSEntry.ReqInfo.Tx.Tx, &staticDNSEntry.TypeID, "staticdnsentry")
 	if err != nil {
-		return err
+		return err, nil
 	}
 
 	var addressErr, ttlErr error
@@ -135,7 +135,7 @@ func (staticDNSEntry TOStaticDNSEntry) Validate() error {
 		"ttl":               ttlErr,
 		"typeId":            validation.Validate(staticDNSEntry.TypeID, validation.Required),
 	}
-	return util.JoinErrs(tovalidate.ToErrors(errs))
+	return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 }
 
 func (en *TOStaticDNSEntry) Read(h http.Header, useIMS bool) ([]interface{}, error, error, int, *time.Time) {
diff --git a/traffic_ops/traffic_ops_golang/staticdnsentry/staticdnsentry_test.go b/traffic_ops/traffic_ops_golang/staticdnsentry/staticdnsentry_test.go
index c0c34eaf90..40a08c45da 100644
--- a/traffic_ops/traffic_ops_golang/staticdnsentry/staticdnsentry_test.go
+++ b/traffic_ops/traffic_ops_golang/staticdnsentry/staticdnsentry_test.go
@@ -89,7 +89,8 @@ func TestValidate(t *testing.T) {
 	reqInfo := api.APIInfo{Tx: tx}
 	// invalid name, empty domainname
 	ts := TOStaticDNSEntry{APIInfoImpl: api.APIInfoImpl{ReqInfo: &reqInfo}}
-	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(ts.Validate())))
+	err, _ = ts.Validate()
+	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(err)))
 
 	expectedErrs := util.JoinErrsStr([]error{
 		errors.New(`'address' cannot be blank`),
diff --git a/traffic_ops/traffic_ops_golang/status/statuses.go b/traffic_ops/traffic_ops_golang/status/statuses.go
index 875edce434..24f7bc38cc 100644
--- a/traffic_ops/traffic_ops_golang/status/statuses.go
+++ b/traffic_ops/traffic_ops_golang/status/statuses.go
@@ -94,11 +94,11 @@ func (status TOStatus) GetAuditName() string {
 
 func (status TOStatus) GetType() string { return "status" }
 
-func (status TOStatus) Validate() error {
+func (status TOStatus) Validate() (error, error) {
 	errs := validation.Errors{
 		"name": validation.Validate(status.Name, validation.NotNil, validation.Required),
 	}
-	return util.JoinErrs(tovalidate.ToErrors(errs))
+	return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 }
 
 func (st *TOStatus) Read(h http.Header, useIMS bool) ([]interface{}, error, error, int, *time.Time) {
diff --git a/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets.go b/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets.go
index 091d6cb9da..46023832c1 100644
--- a/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets.go
+++ b/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets.go
@@ -94,7 +94,7 @@ func (st TOSteeringTargetV11) GetType() string {
 	return "steeringtarget"
 }
 
-func (st TOSteeringTargetV11) Validate() error {
+func (st TOSteeringTargetV11) Validate() (error, error) {
 	return st.SteeringTargetNullable.Validate(st.ReqInfo.Tx.Tx)
 }
 
diff --git a/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets_test.go b/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets_test.go
index 2a79a232a0..fe8fe0074a 100644
--- a/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets_test.go
+++ b/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets_test.go
@@ -20,12 +20,13 @@ package steeringtargets
  */
 
 import (
+	"testing"
+
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/jmoiron/sqlx"
 	"gopkg.in/DATA-DOG/go-sqlmock.v1"
-	"testing"
 )
 
 func TestInvalidSteeringTargetType(t *testing.T) {
@@ -82,7 +83,7 @@ func TestInvalidSteeringTargetType(t *testing.T) {
 		LastUpdated:            nil,
 	}
 
-	err = stObj.Validate()
+	err, _ = stObj.Validate()
 	if err == nil {
 		t.Fatal("expected user error to say that type is invalid, got no error instead")
 	}
diff --git a/traffic_ops/traffic_ops_golang/topology/topologies.go b/traffic_ops/traffic_ops_golang/topology/topologies.go
index f9fb81dea7..df9ead8bbd 100644
--- a/traffic_ops/traffic_ops_golang/topology/topologies.go
+++ b/traffic_ops/traffic_ops_golang/topology/topologies.go
@@ -89,7 +89,7 @@ func (topology *TOTopology) GetType() string {
 }
 
 // Validate is a requirement of the api.Validator interface.
-func (topology *TOTopology) Validate() error {
+func (topology *TOTopology) Validate() (error, error) {
 	currentTopoName := topology.APIInfoImpl.ReqInfo.Params["name"]
 	nameRule := validation.NewStringRule(tovalidate.IsAlphanumericUnderscoreDash, "must consist of only alphanumeric, dash, or underscore characters.")
 	rules := validation.Errors{}
@@ -117,12 +117,7 @@ func (topology *TOTopology) Validate() error {
 		cacheGroupNames[index] = node.Cachegroup
 	}
 	if cacheGroupMap, userErr, sysErr, _ = cachegroup.GetCacheGroupsByName(cacheGroupNames, topology.APIInfoImpl.ReqInfo.Tx); userErr != nil || sysErr != nil {
-		var err error
-		message := "could not get cachegroups"
-		if userErr != nil {
-			err = fmt.Errorf("%s: %s", message, userErr.Error())
-		}
-		return err
+		return userErr, sysErr
 	}
 	cacheGroups = make([]tc.CacheGroupNullable, len(topology.Nodes))
 	for index, node := range topology.Nodes {
@@ -133,7 +128,7 @@ func (topology *TOTopology) Validate() error {
 	}
 	rules["duplicate cachegroup name"] = checkUniqueCacheGroupNames(topology.Nodes)
 	if !cacheGroupsExist {
-		return util.JoinErrs(tovalidate.ToErrors(rules))
+		return util.JoinErrs(tovalidate.ToErrors(rules)), nil
 	}
 
 	for index, node := range topology.Nodes {
@@ -146,8 +141,7 @@ func (topology *TOTopology) Validate() error {
 	}
 	dsCDNs, err := dbhelpers.GetDeliveryServiceCDNsByTopology(topology.ReqInfo.Tx.Tx, currentTopoName)
 	if err != nil {
-		log.Errorf("validating topology: %v", err)
-		return errors.New("unable to validate topology")
+		return errors.New("unable to validate topology"), fmt.Errorf("validating Topology: %w", err)
 	}
 	rules["empty cachegroups"] = topology_validation.CheckForEmptyCacheGroups(topology.ReqInfo.Tx, cacheGroupIds, dsCDNs, false, nil)
 	rules["required capabilities"] = topology.validateDSRequiredCapabilities(currentTopoName)
@@ -155,17 +149,13 @@ func (topology *TOTopology) Validate() error {
 	//Get current Topology-CG for the requested change.
 	topoCachegroupNames := topology.getCachegroupNames()
 	userErr, sysErr, _ = dbhelpers.CheckTopologyOrgServerCGInDSCG(topology.ReqInfo.Tx.Tx, dsCDNs, currentTopoName, topoCachegroupNames)
-	if userErr != nil {
-		return userErr
-	}
-	if sysErr != nil {
-		log.Errorf("error while validate topology: %s", sysErr.Error())
-		return errors.New("unable to validate topology")
+	if userErr != nil || sysErr != nil {
+		return userErr, sysErr
 	}
 
 	/* Only perform further checks if everything so far is valid */
 	if err = util.JoinErrs(tovalidate.ToErrors(rules)); err != nil {
-		return err
+		return err, nil
 	}
 
 	for _, leafMid := range checkForLeafMids(topology.Nodes, cacheGroups) {
@@ -174,7 +164,7 @@ func (topology *TOTopology) Validate() error {
 	_, rules["topology cycles"] = checkForCycles(topology.Nodes)
 	rules["super-topology cycles"] = topology.checkForCyclesAcrossTopologies()
 
-	return util.JoinErrs(tovalidate.ToErrors(rules))
+	return util.JoinErrs(tovalidate.ToErrors(rules)), nil
 }
 
 func (topology *TOTopology) nodesInOtherTopologies() ([]tc.TopologyNode, map[string][]string, error) {
diff --git a/traffic_ops/traffic_ops_golang/types/types.go b/traffic_ops/traffic_ops_golang/types/types.go
index 8229779ecb..f16051c725 100644
--- a/traffic_ops/traffic_ops_golang/types/types.go
+++ b/traffic_ops/traffic_ops_golang/types/types.go
@@ -94,16 +94,16 @@ func (typ *TOType) GetType() string {
 	return "type"
 }
 
-func (typ *TOType) Validate() error {
+func (typ *TOType) Validate() (error, 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 util.JoinErrs(tovalidate.ToErrors(errs))
+		return util.JoinErrs(tovalidate.ToErrors(errs)), nil
 	}
-	return nil
+	return nil, nil
 }
 
 func (tp *TOType) Read(h http.Header, useIMS bool) ([]interface{}, error, error, int, *time.Time) {
diff --git a/traffic_ops/traffic_ops_golang/types/types_test.go b/traffic_ops/traffic_ops_golang/types/types_test.go
index 529417de56..3ad395401c 100644
--- a/traffic_ops/traffic_ops_golang/types/types_test.go
+++ b/traffic_ops/traffic_ops_golang/types/types_test.go
@@ -181,7 +181,8 @@ func TestCreateInvalidType(t *testing.T) {
 
 func TestValidate(t *testing.T) {
 	p := TOType{}
-	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(p.Validate())))
+	err, _ := p.Validate()
+	errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(err)))
 	expected := util.JoinErrsStr(test.SortErrors([]error{
 		errors.New("'name' cannot be blank"),
 		errors.New("'description' cannot be blank"),
diff --git a/traffic_ops/traffic_ops_golang/user/user.go b/traffic_ops/traffic_ops_golang/user/user.go
index 8cd9df7b75..89b3fea9a2 100644
--- a/traffic_ops/traffic_ops_golang/user/user.go
+++ b/traffic_ops/traffic_ops_golang/user/user.go
@@ -40,7 +40,7 @@ import (
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/util/ims"
 
-	"github.com/go-ozzo/ozzo-validation"
+	validation "github.com/go-ozzo/ozzo-validation"
 	"github.com/go-ozzo/ozzo-validation/is"
 	"github.com/jmoiron/sqlx"
 )
@@ -97,7 +97,7 @@ func (user *TOUser) ParamColumns() map[string]dbhelpers.WhereColumnInfo {
 	}
 }
 
-func (user *TOUser) Validate() error {
+func (user *TOUser) Validate() (error, error) {
 
 	validateErrs := validation.Errors{
 		"email":    validation.Validate(user.Email, validation.Required, is.Email),
@@ -111,11 +111,11 @@ func (user *TOUser) Validate() error {
 	if user.LocalPassword != nil {
 		_, err := auth.IsGoodLoginPair(*user.Username, *user.LocalPassword)
 		if err != nil {
-			return err
+			return err, nil
 		}
 	}
 
-	return util.JoinErrs(tovalidate.ToErrors(validateErrs))
+	return util.JoinErrs(tovalidate.ToErrors(validateErrs)), nil
 }
 
 func (user *TOUser) postValidate() error {