errors.go raw

   1  // Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates.  All rights reserved.
   2  // This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
   3  
   4  package common
   5  
   6  import (
   7  	"encoding/json"
   8  	"errors"
   9  	"fmt"
  10  	"io/ioutil"
  11  	"net"
  12  	"net/http"
  13  	"strings"
  14  	"syscall"
  15  
  16  	"github.com/sony/gobreaker"
  17  )
  18  
  19  // ServiceError models all potential errors generated the service call
  20  type ServiceError interface {
  21  	// The http status code of the error
  22  	GetHTTPStatusCode() int
  23  
  24  	// The human-readable error string as sent by the service
  25  	GetMessage() string
  26  
  27  	// A short error code that defines the error, meant for programmatic parsing.
  28  	// See https://docs.oracle.com/iaas/Content/API/References/apierrors.htm
  29  	GetCode() string
  30  
  31  	// Unique Oracle-assigned identifier for the request.
  32  	// If you need to contact Oracle about a particular request, please provide the request ID.
  33  	GetOpcRequestID() string
  34  }
  35  
  36  // ServiceErrorRichInfo models all potential errors generated the service call and contains rich info for debugging purpose
  37  type ServiceErrorRichInfo interface {
  38  	ServiceError
  39  	// The service this service call is sending to
  40  	GetTargetService() string
  41  
  42  	// The API name this service call is sending to
  43  	GetOperationName() string
  44  
  45  	// The timestamp when this request is made
  46  	GetTimestamp() SDKTime
  47  
  48  	// The endpoint and the Http method of this service call
  49  	GetRequestTarget() string
  50  
  51  	// The client version, in this case the oci go sdk version
  52  	GetClientVersion() string
  53  
  54  	// The API reference doc link for this API, optional and maybe empty
  55  	GetOperationReferenceLink() string
  56  
  57  	// Troubleshooting doc link
  58  	GetErrorTroubleshootingLink() string
  59  }
  60  
  61  // ServiceErrorLocalizationMessage models all potential errors generated the service call and has localized error message info
  62  type ServiceErrorLocalizationMessage interface {
  63  	ServiceErrorRichInfo
  64  	// The original error message string as sent by the service
  65  	GetOriginalMessage() string
  66  
  67  	// The values to be substituted into the originalMessageTemplate, expressed as a string-to-string map.
  68  	GetMessageArgument() map[string]string
  69  
  70  	// Template in ICU MessageFormat for the human-readable error string in English, but without the values replaced
  71  	GetOriginalMessageTemplate() string
  72  }
  73  
  74  type servicefailure struct {
  75  	StatusCode              int
  76  	Code                    string            `json:"code,omitempty"`
  77  	Message                 string            `json:"message,omitempty"`
  78  	OriginalMessage         string            `json:"originalMessage"`
  79  	OriginalMessageTemplate string            `json:"originalMessageTemplate"`
  80  	MessageArgument         map[string]string `json:"messageArguments"`
  81  	OpcRequestID            string            `json:"opc-request-id"`
  82  	// debugging information
  83  	TargetService string  `json:"target-service"`
  84  	OperationName string  `json:"operation-name"`
  85  	Timestamp     SDKTime `json:"timestamp"`
  86  	RequestTarget string  `json:"request-target"`
  87  	ClientVersion string  `json:"client-version"`
  88  
  89  	// troubleshooting guidance
  90  	OperationReferenceLink   string `json:"operation-reference-link"`
  91  	ErrorTroubleshootingLink string `json:"error-troubleshooting-link"`
  92  }
  93  
  94  func newServiceFailureFromResponse(response *http.Response) error {
  95  	var err error
  96  	var timestamp SDKTime
  97  	t, err := tryParsingTimeWithValidFormatsForHeaders([]byte(response.Header.Get("Date")), "Date")
  98  
  99  	if err != nil {
 100  		timestamp = *now()
 101  	} else {
 102  		timestamp = sdkTimeFromTime(t)
 103  	}
 104  
 105  	se := servicefailure{
 106  		StatusCode:    response.StatusCode,
 107  		Code:          "BadErrorResponse",
 108  		OpcRequestID:  response.Header.Get("opc-request-id"),
 109  		Timestamp:     timestamp,
 110  		ClientVersion: defaultSDKMarker + "/" + Version(),
 111  		RequestTarget: fmt.Sprintf("%s %s", response.Request.Method, response.Request.URL),
 112  	}
 113  
 114  	//If there is an error consume the body, entirely
 115  	body, err := ioutil.ReadAll(response.Body)
 116  	if err != nil {
 117  		se.Message = fmt.Sprintf("The body of the response was not readable, due to :%s", err.Error())
 118  		return se
 119  	}
 120  
 121  	err = json.Unmarshal(body, &se)
 122  	if err != nil {
 123  		Debugf("Error response could not be parsed due to: %s", err.Error())
 124  		se.Message = fmt.Sprintf("Failed to parse json from response body due to: %s. With response body %s.", err.Error(), string(body[:]))
 125  		return se
 126  	}
 127  	return se
 128  }
 129  
 130  // PostProcessServiceError process the service error after an error is raised and complete it with extra information
 131  func PostProcessServiceError(err error, service string, method string, apiReferenceLink string) error {
 132  	var serviceFailure servicefailure
 133  	if _, ok := err.(servicefailure); !ok {
 134  		return err
 135  	}
 136  	serviceFailure = err.(servicefailure)
 137  	serviceFailure.OperationName = method
 138  	serviceFailure.TargetService = service
 139  	serviceFailure.ErrorTroubleshootingLink = fmt.Sprintf("https://docs.oracle.com/iaas/Content/API/References/apierrors.htm#apierrors_%v__%v_%s", serviceFailure.StatusCode, serviceFailure.StatusCode, strings.ToLower(serviceFailure.Code))
 140  	serviceFailure.OperationReferenceLink = apiReferenceLink
 141  	return serviceFailure
 142  }
 143  
 144  func (se servicefailure) Error() string {
 145  	return fmt.Sprintf(`Error returned by %s Service. Http Status Code: %d. Error Code: %s. Opc request id: %s. Message: %s
 146  Operation Name: %s
 147  Timestamp: %s
 148  Client Version: %s
 149  Request Endpoint: %s
 150  Troubleshooting Tips: See %s for more information about resolving this error.%s
 151  To get more info on the failing request, you can set OCI_GO_SDK_DEBUG env var to info or higher level to log the request/response details.
 152  If you are unable to resolve this %s issue, please contact Oracle support and provide them this full error message.`,
 153  		se.TargetService, se.StatusCode, se.Code, se.OpcRequestID, se.Message, se.OperationName, se.Timestamp, se.ClientVersion, se.RequestTarget, se.ErrorTroubleshootingLink, se.getOperationReferenceMessage(), se.TargetService)
 154  }
 155  
 156  func (se servicefailure) getOperationReferenceMessage() string {
 157  	if se.OperationReferenceLink == "" {
 158  		return ""
 159  	}
 160  	return fmt.Sprintf("\nAlso see %s for details on this operation's requirements.", se.OperationReferenceLink)
 161  }
 162  
 163  func (se servicefailure) GetHTTPStatusCode() int {
 164  	return se.StatusCode
 165  
 166  }
 167  
 168  func (se servicefailure) GetMessage() string {
 169  	return se.Message
 170  }
 171  
 172  func (se servicefailure) GetOriginalMessage() string {
 173  	return se.OriginalMessage
 174  }
 175  
 176  func (se servicefailure) GetOriginalMessageTemplate() string {
 177  	return se.OriginalMessageTemplate
 178  }
 179  
 180  func (se servicefailure) GetMessageArgument() map[string]string {
 181  	return se.MessageArgument
 182  }
 183  
 184  func (se servicefailure) GetCode() string {
 185  	return se.Code
 186  }
 187  
 188  func (se servicefailure) GetOpcRequestID() string {
 189  	return se.OpcRequestID
 190  }
 191  
 192  func (se servicefailure) GetTargetService() string {
 193  	return se.TargetService
 194  }
 195  
 196  func (se servicefailure) GetOperationName() string {
 197  	return se.OperationName
 198  }
 199  
 200  func (se servicefailure) GetTimestamp() SDKTime {
 201  	return se.Timestamp
 202  }
 203  
 204  func (se servicefailure) GetRequestTarget() string {
 205  	return se.RequestTarget
 206  }
 207  
 208  func (se servicefailure) GetClientVersion() string {
 209  	return se.ClientVersion
 210  }
 211  
 212  func (se servicefailure) GetOperationReferenceLink() string {
 213  	return se.OperationReferenceLink
 214  }
 215  
 216  func (se servicefailure) GetErrorTroubleshootingLink() string {
 217  	return se.ErrorTroubleshootingLink
 218  }
 219  
 220  // IsServiceError returns false if the error is not service side, otherwise true
 221  // additionally it returns an interface representing the ServiceError
 222  func IsServiceError(err error) (failure ServiceError, ok bool) {
 223  	failure, ok = err.(ServiceError)
 224  	return
 225  }
 226  
 227  // IsServiceErrorRichInfo returns false if the error is not service side or is not containing rich info, otherwise true
 228  // additionally it returns an interface representing the ServiceErrorRichInfo
 229  func IsServiceErrorRichInfo(err error) (failure ServiceErrorRichInfo, ok bool) {
 230  	failure, ok = err.(ServiceErrorRichInfo)
 231  	return
 232  }
 233  
 234  // IsServiceErrorLocalizationMessage returns false if the error is not service side, otherwise true
 235  // additionally it returns an interface representing the ServiceErrorOriginalMessage
 236  func IsServiceErrorLocalizationMessage(err error) (failure ServiceErrorLocalizationMessage, ok bool) {
 237  	failure, ok = err.(ServiceErrorLocalizationMessage)
 238  	return
 239  }
 240  
 241  type deadlineExceededByBackoffError struct{}
 242  
 243  func (deadlineExceededByBackoffError) Error() string {
 244  	return "now() + computed backoff duration exceeds request deadline"
 245  }
 246  
 247  // DeadlineExceededByBackoff is the error returned by Call() when GetNextDuration() returns a time.Duration that would
 248  // force the user to wait past the request deadline before re-issuing a request. This enables us to exit early, since
 249  // we cannot succeed based on the configured retry policy.
 250  var DeadlineExceededByBackoff error = deadlineExceededByBackoffError{}
 251  
 252  // NonSeekableRequestRetryFailure is the error returned when the request is with binary request body, and is configured
 253  // retry, but the request body is not retryable
 254  type NonSeekableRequestRetryFailure struct {
 255  	err error
 256  }
 257  
 258  func (ne NonSeekableRequestRetryFailure) Error() string {
 259  	if ne.err == nil {
 260  		return "Unable to perform Retry on this request body type, which did not implement seek() interface"
 261  	}
 262  	return fmt.Sprintf("%s. Unable to perform Retry on this request body type, which did not implement seek() interface", ne.err.Error())
 263  }
 264  
 265  // IsNetworkError validates if an error is a net.Error and check if it's temporary or timeout
 266  func IsNetworkError(err error) bool {
 267  	if err == nil {
 268  		return false
 269  	}
 270  
 271  	if errors.Is(err, syscall.ECONNRESET) {
 272  		return true
 273  	}
 274  
 275  	if r, ok := err.(net.Error); ok && (r.Timeout() || strings.Contains(err.Error(), "net/http: HTTP/1.x transport connection broken")) {
 276  		return true
 277  	}
 278  
 279  	return false
 280  }
 281  
 282  // IsCircuitBreakerError validates if an error's text is Open state ErrOpenState or HalfOpen state ErrTooManyRequests
 283  func IsCircuitBreakerError(err error) bool {
 284  	if err == nil {
 285  		return false
 286  	}
 287  
 288  	if err.Error() == gobreaker.ErrOpenState.Error() || err.Error() == gobreaker.ErrTooManyRequests.Error() {
 289  		return true
 290  	}
 291  	return false
 292  }
 293  
 294  func getCircuitBreakerError(request *http.Request, err error, cbr *OciCircuitBreaker) error {
 295  	cbErr := fmt.Errorf("%s, so this request was not sent to the %s service.\n\n The circuit breaker was opened because the %s service failed too many times recently. "+
 296  		"Because the circuit breaker has been opened, requests within a %.2f second window of when the circuit breaker opened will not be sent to the %s service.\n\n"+
 297  		"URL which circuit breaker prevented request to - %s \n Circuit Breaker Info \n Name - %s \n State - %s \n\n Errors from %s service which opened the circuit breaker:\n\n%s",
 298  		err, cbr.Cbst.serviceName, cbr.Cbst.serviceName, cbr.Cbst.openStateWindow.Seconds(), cbr.Cbst.serviceName, request.URL.Host+request.URL.Path, cbr.Cbst.name, cbr.Cb.State().String(), cbr.Cbst.serviceName, cbr.GetHistory())
 299  	return cbErr
 300  }
 301  
 302  // StatErrCode is a type which wraps error's statusCode and errorCode from service end
 303  type StatErrCode struct {
 304  	statusCode int
 305  	errorCode  string
 306  }
 307