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