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 <[email protected]>
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 {

Reply via email to