You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ro...@apache.org on 2018/09/20 22:48:49 UTC

[trafficcontrol] 01/03: Updated ParseDBError

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

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

commit d8c5447349166548c340c5e1bb38733ce50a413e
Author: moltzaum <ma...@moltzau.net>
AuthorDate: Thu Sep 20 11:09:48 2018 -0600

    Updated ParseDBError
---
 traffic_ops/traffic_ops_golang/api/api.go          | 28 +++++------
 .../traffic_ops_golang/api/shared_handlers.go      |  4 --
 .../traffic_ops_golang/dbhelpers/db_helpers.go     | 55 +++++++++++++++-------
 3 files changed, 53 insertions(+), 34 deletions(-)

diff --git a/traffic_ops/traffic_ops_golang/api/api.go b/traffic_ops/traffic_ops_golang/api/api.go
index f94fb84..ec152a0 100644
--- a/traffic_ops/traffic_ops_golang/api/api.go
+++ b/traffic_ops/traffic_ops_golang/api/api.go
@@ -36,6 +36,7 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 
 	"github.com/jmoiron/sqlx"
 	"github.com/lib/pq"
@@ -418,24 +419,23 @@ func TypeErrToAPIErr(err error, errType tc.ApiErrorType) (error, error, int) {
 // ParseDBErr parses pq errors for uniqueness constraint violations, and returns the (userErr, sysErr, httpCode) format expected by the API helpers.
 // The dataType is the name of the API object, e.g. 'coordinate' or 'delivery service', used to construct the error string.
 func ParseDBErr(ierr error, dataType string) (error, error, int) {
+
 	err, ok := ierr.(*pq.Error)
 	if !ok {
 		return nil, errors.New("database returned non pq error: " + err.Error()), http.StatusInternalServerError
 	}
-	if len(err.Constraint) > 0 && len(err.Detail) > 0 { //we only want to continue parsing if it is a constraint error with details
-		detail := err.Detail
-		if strings.HasPrefix(detail, "Key ") && strings.HasSuffix(detail, " already exists.") { //we only want to continue parsing if it is a uniqueness constraint error
-			detail = strings.TrimPrefix(detail, "Key ")
-			detail = strings.TrimSuffix(detail, " already exists.")
-			//should look like "(column)=(dupe value)" at this point
-			details := strings.Split(detail, "=")
-			if len(details) == 2 {
-				column := strings.Trim(details[0], "()")
-				dupValue := strings.Trim(details[1], "()")
-				return errors.New("a " + dataType + " with " + column + " " + dupValue + " already exists."), nil, http.StatusBadRequest
-			}
-		}
+
+	if usrErr, sysErr, errCode := TypeErrToAPIErr(dbhelpers.ParsePQNotNullConstraintError(err)); errCode != http.StatusOK {
+		return usrErr, sysErr, errCode
+	}
+
+	if usrErr, sysErr, errCode := TypeErrToAPIErr(dbhelpers.ParsePQPresentFKConstraintError(err)); errCode != http.StatusOK {
+		return usrErr, sysErr, errCode
+	}
+
+	if usrErr, sysErr, errCode := TypeErrToAPIErr(dbhelpers.ParsePQUniqueConstraintError(err)); errCode != http.StatusOK {
+		return usrErr, sysErr, errCode
 	}
-	log.Errorln(dataType + " failed to parse unique constraint from pq error: " + err.Error())
+
 	return nil, err, http.StatusInternalServerError
 }
diff --git a/traffic_ops/traffic_ops_golang/api/shared_handlers.go b/traffic_ops/traffic_ops_golang/api/shared_handlers.go
index 5a1eb3f..975162b 100644
--- a/traffic_ops/traffic_ops_golang/api/shared_handlers.go
+++ b/traffic_ops/traffic_ops_golang/api/shared_handlers.go
@@ -286,7 +286,6 @@ func DeleteHandler(typeFactory CRUDFactory) http.HandlerFunc {
 			}
 		}
 
-		log.Debugf("calling delete on object: %++v", d) //should have id set now
 		userErr, sysErr, errCode = d.Delete()
 		if userErr != nil || sysErr != nil {
 			HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
@@ -324,9 +323,6 @@ func CreateHandler(typeConstructor CRUDFactory) http.HandlerFunc {
 			return
 		}
 
-		log.Debugf("%++v", i)
-		//now we have a validated local object to insert
-
 		if t, ok := i.(Tenantable); ok {
 			authorized, err := t.IsTenantAuthorized(inf.User)
 			if err != nil {
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
index 0b88870..d3ca854 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
@@ -22,6 +22,8 @@ package dbhelpers
 import (
 	"database/sql"
 	"errors"
+	"fmt"
+	"regexp"
 	"strings"
 
 	"github.com/apache/trafficcontrol/lib/go-log"
@@ -101,24 +103,45 @@ func parseCriteriaAndQueryValues(queryParamsToSQLCols map[string]WhereColumnInfo
 	return criteria, queryValues, errs
 }
 
-//parses pq errors for uniqueness constraint violations
-func ParsePQUniqueConstraintError(err *pq.Error) (error, tc.ApiErrorType) {
-	if len(err.Constraint) > 0 && len(err.Detail) > 0 { //we only want to continue parsing if it is a constraint error with details
-		detail := err.Detail
-		if strings.HasPrefix(detail, "Key ") && strings.HasSuffix(detail, " already exists.") { //we only want to continue parsing if it is a uniqueness constraint error
-			detail = strings.TrimPrefix(detail, "Key ")
-			detail = strings.TrimSuffix(detail, " already exists.")
-			//should look like "(column)=(dupe value)" at this point
-			details := strings.Split(detail, "=")
-			if len(details) == 2 {
-				column := strings.Trim(details[0], "()")
-				dupValue := strings.Trim(details[1], "()")
-				return errors.New(column + " " + dupValue + " already exists."), tc.DataConflictError
-			}
+// small helper function to help with parsing below
+func toCamelCase(str string) string {
+	mutable := []byte(str)
+	for i := 0; i < len(str); i++ {
+		if mutable[i] == '_' && i+1 < len(str) {
+			mutable[i+1] = strings.ToUpper(string(str[i+1]))[0]
 		}
 	}
-	log.Error.Printf("failed to parse unique constraint from pq error: %v", err)
-	return tc.DBError, tc.SystemError
+	return strings.Replace(string(mutable[:]), "_", "", -1)
+}
+
+// parses pq errors for not null constraint
+func ParsePQNotNullConstraintError(err *pq.Error) (error, tc.ApiErrorType) {
+	pattern := regexp.MustCompile(`null value in column "(.+)" violates not-null constraint`)
+	match := pattern.FindStringSubmatch(err.Message)
+	if match == nil {
+		return nil, tc.NoError
+	}
+	return fmt.Errorf("%s is a required field", toCamelCase(match[1])), tc.DataConflictError
+}
+
+// parses pq errors for violated foreign key constraints
+func ParsePQPresentFKConstraintError(err *pq.Error) (error, tc.ApiErrorType) {
+	pattern := regexp.MustCompile(`Key \(.+\)=\(.+\) is not present in table "(.+)"`)
+	match := pattern.FindStringSubmatch(err.Detail)
+	if match == nil {
+		return nil, tc.NoError
+	}
+	return fmt.Errorf("%s not found", match[1]), tc.DataMissingError
+}
+
+// parses pq errors for uniqueness constraint violations
+func ParsePQUniqueConstraintError(err *pq.Error) (error, tc.ApiErrorType) {
+	pattern := regexp.MustCompile(`Key \((.+)\)=\((.+)\) already exists`)
+	match := pattern.FindStringSubmatch(err.Detail)
+	if match == nil {
+		return nil, tc.NoError
+	}
+	return fmt.Errorf("%s %s already exists.", match[1], match[2]), tc.DataConflictError
 }
 
 // FinishTx commits the transaction if commit is true when it's called, otherwise it rolls back the transaction. This is designed to be called in a defer.