...

Source file src/github.com/ory/x/sqlcon/error.go

Documentation: github.com/ory/x/sqlcon

     1  package sqlcon
     2  
     3  import (
     4  	"database/sql"
     5  	"net/http"
     6  	"strings"
     7  
     8  	"google.golang.org/grpc/codes"
     9  
    10  	"github.com/go-sql-driver/mysql"
    11  	"github.com/jackc/pgconn"
    12  	"github.com/lib/pq"
    13  	"github.com/pkg/errors"
    14  
    15  	"github.com/ory/herodot"
    16  
    17  	"github.com/ory/x/errorsx"
    18  )
    19  
    20  var (
    21  	// ErrUniqueViolation is returned when^a SQL INSERT / UPDATE command returns a conflict.
    22  	ErrUniqueViolation = &herodot.DefaultError{
    23  		CodeField:     http.StatusConflict,
    24  		GRPCCodeField: codes.AlreadyExists,
    25  		StatusField:   http.StatusText(http.StatusConflict),
    26  		ErrorField:    "Unable to insert or update resource because a resource with that value exists already",
    27  	}
    28  	// ErrNoRows is returned when a SQL SELECT statement returns no rows.
    29  	ErrNoRows = &herodot.DefaultError{
    30  		CodeField:     http.StatusNotFound,
    31  		GRPCCodeField: codes.NotFound,
    32  		StatusField:   http.StatusText(http.StatusNotFound),
    33  		ErrorField:    "Unable to locate the resource",
    34  	}
    35  	// ErrConcurrentUpdate is returned when the database is unable to serialize access due to a concurrent update.
    36  	ErrConcurrentUpdate = &herodot.DefaultError{
    37  		CodeField:     http.StatusBadRequest,
    38  		GRPCCodeField: codes.Aborted,
    39  		StatusField:   http.StatusText(http.StatusBadRequest),
    40  		ErrorField:    "Unable to serialize access due to a concurrent update in another session",
    41  	}
    42  )
    43  
    44  // HandleError returns the right sqlcon.Err* depending on the input error.
    45  func HandleError(err error) error {
    46  	if err == nil {
    47  		return nil
    48  	}
    49  
    50  	if errors.Is(err, sql.ErrNoRows) {
    51  		return errors.WithStack(ErrNoRows)
    52  	}
    53  
    54  	switch e := errorsx.Cause(err).(type) {
    55  	case interface{ SQLState() string }:
    56  		switch e.SQLState() {
    57  		case "23505": // "unique_violation"
    58  			return errors.Wrap(ErrUniqueViolation, err.Error())
    59  		case "40001": // "serialization_failure"
    60  			return errors.Wrap(ErrConcurrentUpdate, err.Error())
    61  		}
    62  	case *pq.Error:
    63  		switch e.Code {
    64  		case "23505": // "unique_violation"
    65  			return errors.Wrap(ErrUniqueViolation, e.Error())
    66  		case "40001": // "serialization_failure"
    67  			return errors.Wrap(ErrConcurrentUpdate, e.Error())
    68  		}
    69  	case *mysql.MySQLError:
    70  		switch e.Number {
    71  		case 1062:
    72  			return errors.Wrap(ErrUniqueViolation, err.Error())
    73  		}
    74  	case *pgconn.PgError:
    75  		switch e.Code {
    76  		case "23505": // "unique_violation"
    77  			return errors.Wrap(ErrUniqueViolation, e.Error())
    78  		case "40001": // "serialization_failure"
    79  			return errors.Wrap(ErrConcurrentUpdate, e.Error())
    80  		}
    81  	}
    82  
    83  	// Try other detections, for example for SQLite (we don't want to enforce CGO here!)
    84  	if strings.Contains(err.Error(), "UNIQUE constraint failed") {
    85  		return errors.Wrap(ErrUniqueViolation, err.Error())
    86  	}
    87  
    88  	return errors.WithStack(err)
    89  }
    90  

View as plain text