...

Source file src/edge-infra.dev/pkg/edge/api/apierror/sql/sql_error.go

Documentation: edge-infra.dev/pkg/edge/api/apierror/sql

     1  package sql
     2  
     3  import (
     4  	"database/sql/driver"
     5  	"encoding/json"
     6  	"errors"
     7  	"strings"
     8  
     9  	"github.com/jackc/pgconn"
    10  	"github.com/jackc/pgx/v4"
    11  	"github.com/lib/pq"
    12  
    13  	"edge-infra.dev/pkg/edge/api/sql/utils"
    14  )
    15  
    16  const (
    17  	EdgeSQLErrType = "EDGE_SQL_STATE"
    18  )
    19  
    20  var (
    21  	sqlErrorMapping = map[error]string{
    22  		pgx.ErrNoRows:           "No results found for query",
    23  		pgx.ErrTxClosed:         "The transaction was closed",
    24  		pgx.ErrTxCommitRollback: "Commit resulted in transaction rollback",
    25  		driver.ErrBadConn:       "Bad SQL connection",
    26  	}
    27  	pgErrorMappings = map[pq.ErrorCode]ErrorAndMsg{
    28  		"22P02": {
    29  			Error:   "invalid_text_representation",
    30  			Message: "Invalid input syntax for type",
    31  		},
    32  		"57P04": {
    33  			Error:   "database_dropped",
    34  			Message: "Database has been dropped",
    35  		},
    36  		"58000": {
    37  			Error:   "system_error",
    38  			Message: "Internal System Error",
    39  		},
    40  		"P0002": {
    41  			Error:   "no_data_found",
    42  			Message: "No results found for query",
    43  		},
    44  		"XX000": {
    45  			Error:   "internal_error",
    46  			Message: "Internal System Error",
    47  		},
    48  		"40000": {
    49  			Error:   "transaction_rollback",
    50  			Message: "Transaction has been rolled back",
    51  		},
    52  	}
    53  	specialCases = []SpecialCase{
    54  		{
    55  			Matcher: "destination arguments in Scan",
    56  			Message: "Invalid number of variables in scan statement",
    57  		},
    58  		{
    59  			Matcher: "can't be converted to int32",
    60  			Message: "Error Converting to int32",
    61  		},
    62  		{
    63  			Matcher: "overflows int32",
    64  			Message: "Value overflows int32",
    65  		},
    66  		{
    67  			Matcher: "Scan called without calling Next",
    68  			Message: "Scan statement called without next",
    69  		},
    70  		{
    71  			Matcher: "Scan error on column index",
    72  			Message: "Error scanning on column index",
    73  		},
    74  		{
    75  			Matcher: "Rows are closed",
    76  			Message: "Result rows have been closed",
    77  		},
    78  		{
    79  			Matcher: "no Rows available",
    80  			Message: "No Result rows found",
    81  		},
    82  		{
    83  			Matcher: "statement is closed",
    84  			Message: "Statement has been closed",
    85  		},
    86  		{
    87  			Matcher: "no rows in result set",
    88  			Message: "No results found for query",
    89  		},
    90  	}
    91  )
    92  
    93  type SpecialCase struct {
    94  	Matcher string
    95  	Message string
    96  }
    97  
    98  type ErrorAndMsg struct {
    99  	Error   string
   100  	Message string
   101  }
   102  
   103  type Error struct {
   104  	Err       error
   105  	Message   string
   106  	ErrorCode pq.ErrorCode
   107  	Verbose   string
   108  	ErrorType string
   109  	Severity  string
   110  }
   111  
   112  // New returns a new instance of a SQL error.
   113  func New(msg string) *Error {
   114  	return Wrap(errors.New(msg))
   115  }
   116  
   117  // Wrap returns a new instance of a SQL error from an stdlib error and unmarshals it.
   118  func Wrap(err error) *Error {
   119  	e := &Error{
   120  		Err:       err,
   121  		Message:   err.Error(),
   122  		ErrorType: EdgeSQLErrType,
   123  	}
   124  	return e.unmarshalError(err)
   125  }
   126  
   127  // SetMessage sets the message field of the SQL error.
   128  func (s *Error) SetMessage(msg string) *Error {
   129  	s.Message = msg
   130  	return s
   131  }
   132  
   133  // SetErrorCode sets the error code of the SQL error.
   134  func (s *Error) SetErrorCode(code string) *Error {
   135  	s.ErrorCode = pq.ErrorCode(code)
   136  	return s
   137  }
   138  
   139  // SetErrorType sets the error type of the SQL error.
   140  func (s *Error) SetErrorType(errorType string) *Error {
   141  	s.ErrorType = errorType
   142  	return s
   143  }
   144  
   145  // SetVerbose sets the verbose field with the entire error response returned by SQL.
   146  func (s *Error) SetVerbose(verbose string) *Error {
   147  	s.Verbose = verbose
   148  	return s
   149  }
   150  
   151  // SetSeverity sets the severity field.
   152  func (s *Error) SetSeverity(severity string) *Error {
   153  	s.Severity = severity
   154  	return s
   155  }
   156  
   157  // Error implements the error interface and returns the error message of the SQL error.
   158  func (s *Error) Error() string {
   159  	return s.Message
   160  }
   161  
   162  // Unwrap converts a BSL error to an stdlib error.
   163  func (s *Error) Unwrap() error {
   164  	if s != nil {
   165  		return s.Err
   166  	}
   167  	return nil
   168  }
   169  
   170  // unmarshalError unmarshals the error and sets the appropriate error message,
   171  // verbose, status code and error type.
   172  func (s *Error) unmarshalError(err error) *Error {
   173  	switch err {
   174  	case nil:
   175  		return s
   176  	default:
   177  		s.Err = err
   178  		castErr := utils.CastError(err)
   179  		if val, exists := pgErrorMappings[pq.ErrorCode(castErr.Code)]; exists {
   180  			if val.Message != "" {
   181  				return s.SetMessage(val.Message).SetErrorCode(castErr.Code).SetVerbose(toVerbose(castErr)).SetSeverity(castErr.Severity)
   182  			}
   183  			return s.SetMessage(val.Error).SetErrorCode(castErr.Code).SetVerbose(toVerbose(castErr)).SetSeverity(castErr.Severity)
   184  		}
   185  		//error code does not exists in pgErrorMappings check the sqlErrorMappings
   186  		if val, exists := sqlErrorMapping[err]; exists {
   187  			return s.SetMessage(val).SetErrorCode(castErr.Code).SetVerbose(toVerbose(castErr)).SetSeverity(castErr.Severity)
   188  		}
   189  		//if no matches after then check lose matches for errors like
   190  		//"sql: expected 10 destination arguments in Scan, not 9"
   191  		for i := range specialCases {
   192  			if strings.Contains(err.Error(), specialCases[i].Matcher) {
   193  				return s.SetMessage(specialCases[i].Message).SetErrorCode(castErr.Code).SetVerbose(toVerbose(castErr)).SetSeverity(castErr.Severity)
   194  			}
   195  		}
   196  		return s.SetErrorCode("Unknown")
   197  	}
   198  }
   199  
   200  // Extensions builds the error extensions that are displayed in the additional field in the graphql error response.
   201  func (s *Error) Extensions() map[string]interface{} {
   202  	return map[string]interface{}{
   203  		"severity":   s.Severity,
   204  		"statusCode": s.ErrorCode,
   205  		//"verbose":    s.Verbose, we don't want to return implementation details
   206  		"errorType": s.ErrorType,
   207  	}
   208  }
   209  
   210  // toVerbose marshals the pgconn error to a json string for the verbose field.
   211  func toVerbose(err *pgconn.PgError) string {
   212  	res, _ := json.Marshal(err)
   213  	return string(res)
   214  }
   215  

View as plain text