88 "net/http"
99 "time"
1010
11+ "github.com/deploymenttheory/go-api-http-client/internal/errors"
1112 "github.com/deploymenttheory/go-api-http-client/internal/logger"
13+ "go.uber.org/zap"
1214)
1315
1416// DoRequest constructs and executes a standard HTTP request with support for retry logic.
@@ -78,7 +80,7 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}, log l
7880 }
7981
8082 // Construct URL using the ConstructAPIResourceEndpoint function
81- url := apiHandler .ConstructAPIResourceEndpoint (endpoint )
83+ url := apiHandler .ConstructAPIResourceEndpoint (endpoint , log )
8284
8385 // Initialize total request counter
8486 c .PerfMetrics .lock .Lock ()
@@ -103,7 +105,8 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}, log l
103105 req .Header .Set ("User-Agent" , GetUserAgentHeader ())
104106
105107 // Debug: Print request headers if in debug mode
106- log .Debug ("HTTP Request Headers:" , req .Header )
108+ headersStr := HeadersToString (req .Header )
109+ log .Debug ("HTTP Multipart Request Headers:" , zap .String ("headers" , headersStr ))
107110
108111 // Define if request is retryable
109112 retryableHTTPMethods := map [string ]bool {
@@ -133,10 +136,11 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}, log l
133136 // Execute the request
134137 resp , err := c .httpClient .Do (req )
135138 if err != nil {
136- log .Error ("Failed to send multipart request" ,
137- "method" , method ,
138- "endpoint" , endpoint ,
139- "error" , err .Error (),
139+ log .Error ("Failed to send retryable request" ,
140+ zap .String ("method" , method ),
141+ zap .String ("endpoint" , endpoint ),
142+ zap .Int ("status_code" , resp .StatusCode ),
143+ zap .String ("status_text" , http .StatusText (resp .StatusCode )),
140144 )
141145 return nil , err
142146 }
@@ -153,85 +157,88 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}, log l
153157
154158 // Handle (unmarshal) response with API Handler
155159 if err := apiHandler .UnmarshalResponse (resp , out , log ); err != nil {
156- switch e := err .( type ) {
157- case * APIError : // Assuming APIError is a type that includes StatusCode and Message
158- // Log the API error with structured logging
160+ // Use type assertion to check if the error is of type *errors.APIError
161+ if apiErr , ok := err .( * errors. APIError ); ok {
162+ // Log the API error with structured logging for specific APIError handling
159163 log .Error ("Received an API error" ,
160- "method" , method ,
161- "endpoint" , endpoint ,
162- "status_code" , e .StatusCode ,
163- "message" , e .Message ,
164+ zap . String ( "method" , method ) ,
165+ zap . String ( "endpoint" , endpoint ) ,
166+ zap . Int ( "status_code" , apiErr .StatusCode ) ,
167+ zap . String ( "message" , apiErr .Message ) ,
164168 )
165- return resp , e
166- default :
167- // Log the default error with structured logging
169+ return resp , apiErr // Return the typed error for further handling if needed
170+ } else {
171+ // Log other errors with structured logging for general error handling
168172 log .Error ("Failed to unmarshal HTTP response" ,
169- "method" , method ,
170- "endpoint" , endpoint ,
171- "error" , err .Error (), // Convert error to string to log as a value
173+ zap . String ( "method" , method ) ,
174+ zap . String ( "endpoint" , endpoint ) ,
175+ zap .Error (err ), // Use zap.Error to log the error object
172176 )
173177 return resp , err
174178 }
175179 }
176180
181+ // Successful response
177182 if resp .StatusCode >= 200 && resp .StatusCode < 300 {
178- // Log successful HTTP request
179183 log .Info ("HTTP request succeeded" ,
180- "method" , method ,
181- "endpoint" , endpoint ,
182- "status_code" , resp .StatusCode ,
184+ zap . String ( "method" , method ) ,
185+ zap . String ( "endpoint" , endpoint ) ,
186+ zap . Int ( "status_code" , resp .StatusCode ) ,
183187 )
184188 return resp , nil
185- } else if resp .StatusCode == http .StatusNotFound {
186- // Log when resource is not found
189+ } else if
190+ // Resource not found
191+ resp .StatusCode == http .StatusNotFound {
187192 log .Warn ("Resource not found" ,
188- "method" , method ,
189- "endpoint" , endpoint ,
190- "status_code" , resp .StatusCode ,
193+ zap . String ( "method" , method ) ,
194+ zap . String ( "endpoint" , endpoint ) ,
195+ zap . Int ( "status_code" , resp .StatusCode ) ,
191196 )
192- return resp , fmt .Errorf ("resource not found: %s" , endpoint )
197+ // Use a centralized method for handling not found error
198+ return resp , err
193199 }
194200
195201 // Retry Logic
196- if IsNonRetryableError (resp ) {
202+ // Non-retryable error
203+ if errors .IsNonRetryableError (resp ) {
197204 log .Warn ("Encountered a non-retryable error" ,
198- "method" , method ,
199- "endpoint" , endpoint ,
200- "status_code" , resp .StatusCode ,
201- "description" , TranslateStatusCode (resp .StatusCode ),
205+ zap . String ( "method" , method ) ,
206+ zap . String ( "endpoint" , endpoint ) ,
207+ zap . Int ( "status_code" , resp .StatusCode ) ,
208+ zap . String ( "description" , errors . TranslateStatusCode (resp .StatusCode ) ),
202209 )
203- return resp , c .HandleAPIError (resp )
204- } else if IsRateLimitError (resp ) {
210+ return resp , errors .HandleAPIError (resp , log ) // Assume this method logs the error internally
211+ } else if errors . IsRateLimitError (resp ) {
205212 waitDuration := parseRateLimitHeaders (resp ) // Parses headers to determine wait duration
206213 log .Warn ("Encountered a rate limit error. Retrying after wait duration." ,
207- "method" , method ,
208- "endpoint" , endpoint ,
209- "status_code" , resp .StatusCode ,
210- "wait_duration" , waitDuration ,
214+ zap . String ( "method" , method ) ,
215+ zap . String ( "endpoint" , endpoint ) ,
216+ zap . Int ( "status_code" , resp .StatusCode ) ,
217+ zap . Duration ( "wait_duration" , waitDuration ) ,
211218 )
212219 time .Sleep (waitDuration )
213220 i ++
214221 continue // This will restart the loop, effectively "retrying" the request
215- } else if IsTransientError (resp ) {
222+ } else if errors . IsTransientError (resp ) {
216223 waitDuration := calculateBackoff (i ) // Calculates backoff duration
217224 log .Warn ("Encountered a transient error. Retrying after backoff." ,
218- "method" , method ,
219- "endpoint" , endpoint ,
220- "status_code" , resp .StatusCode ,
221- "wait_duration" , waitDuration ,
222- "attempt" , i ,
225+ zap . String ( "method" , method ) ,
226+ zap . String ( "endpoint" , endpoint ) ,
227+ zap . Int ( "status_code" , resp .StatusCode ) ,
228+ zap . Duration ( "wait_duration" , waitDuration ) ,
229+ zap . Int ( "attempt" , i ) ,
223230 )
224231 time .Sleep (waitDuration )
225232 i ++
226233 continue // This will restart the loop, effectively "retrying" the request
227234 } else {
228235 log .Error ("Received unexpected error status from HTTP request" ,
229- "method" , method ,
230- "endpoint" , endpoint ,
231- "status_code" , resp .StatusCode ,
232- "description" , TranslateStatusCode (resp .StatusCode ),
236+ zap . String ( "method" , method ) ,
237+ zap . String ( "endpoint" , endpoint ) ,
238+ zap . Int ( "status_code" , resp .StatusCode ) ,
239+ zap . String ( "description" , errors . TranslateStatusCode (resp .StatusCode ) ),
233240 )
234- return resp , c .HandleAPIError (resp )
241+ return resp , errors .HandleAPIError (resp , log )
235242 }
236243 }
237244 } else {
@@ -242,10 +249,11 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}, log l
242249 // Execute the request
243250 resp , err := c .httpClient .Do (req )
244251 if err != nil {
245- log .Error ("Failed to send multipart request" ,
246- "method" , method ,
247- "endpoint" , endpoint ,
248- "error" , err .Error (),
252+ log .Error ("Failed to send request" ,
253+ zap .String ("method" , method ),
254+ zap .String ("endpoint" , endpoint ),
255+ zap .Int ("status_code" , resp .StatusCode ),
256+ zap .String ("status_text" , http .StatusText (resp .StatusCode )),
249257 )
250258 return nil , err
251259 }
@@ -260,24 +268,24 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}, log l
260268
261269 // Handle (unmarshal) response with API Handler
262270 if err := apiHandler .UnmarshalResponse (resp , out , log ); err != nil {
263- switch e := err .( type ) {
264- case * APIError : // Assuming APIError is a type that includes StatusCode and Message
265- // Log the API error with structured logging
271+ // Use type assertion to check if the error is of type *errors.APIError
272+ if apiErr , ok := err .( * errors. APIError ); ok {
273+ // Log the API error with structured logging for specific APIError handling
266274 log .Error ("Received an API error" ,
267- "method" , method ,
268- "endpoint" , endpoint ,
269- "status_code" , e .StatusCode ,
270- "message" , e .Message ,
275+ zap . String ( "method" , method ) ,
276+ zap . String ( "endpoint" , endpoint ) ,
277+ zap . Int ( "status_code" , apiErr .StatusCode ) ,
278+ zap . String ( "message" , apiErr .Message ) ,
271279 )
272- return resp , e
273- default :
274- // Log the default error with structured logging
280+ return resp , apiErr // Return the typed error for further handling if needed
281+ } else {
282+ // Log other errors with structured logging for general error handling
275283 log .Error ("Failed to unmarshal HTTP response" ,
276- "method" , method ,
277- "endpoint" , endpoint ,
278- "error" , err .Error (), // Convert error to string to log as a value
284+ zap . String ( "method" , method ) ,
285+ zap . String ( "endpoint" , endpoint ) ,
286+ zap .Error (err ), // Use zap.Error to log the error object
279287 )
280- return resp , err
288+ return resp , err // Return the original error
281289 }
282290 }
283291
@@ -287,15 +295,16 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}, log l
287295 return resp , nil
288296 } else {
289297 // Translate the status code to a human-readable description
290- statusDescription := TranslateStatusCode (resp .StatusCode )
291-
292- // Log the error with structured context
293- log .Error ("Received non-success status code from HTTP request" ,
294- "method" , method ,
295- "endpoint" , endpoint ,
296- "status_code" , resp .StatusCode ,
297- "description" , statusDescription ,
298- )
298+ statusDescription := errors .TranslateStatusCode (resp .StatusCode )
299+ if apiErr , ok := err .(* errors.APIError ); ok {
300+ // Log the API error with structured logging for specific APIError handling
301+ log .Error ("Received an API error" ,
302+ zap .String ("method" , method ),
303+ zap .String ("endpoint" , endpoint ),
304+ zap .Int ("status_code" , apiErr .StatusCode ),
305+ zap .String ("message" , apiErr .Message ),
306+ )
307+ }
299308
300309 // Return an error with the status code and its description
301310 return resp , fmt .Errorf ("Error status code: %d - %s" , resp .StatusCode , statusDescription )
@@ -331,9 +340,9 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}, log l
331340//
332341// Note:
333342// The caller should handle closing the response body when successful.
334- func (c * Client ) DoMultipartRequest (method , endpoint string , fields map [string ]string , files map [string ]string , out interface {}) (* http.Response , error ) {
343+ func (c * Client ) DoMultipartRequest (method , endpoint string , fields map [string ]string , files map [string ]string , out interface {}, log logger. Logger ) (* http.Response , error ) {
335344 // Auth Token validation check
336- valid , err := c .ValidAuthTokenCheck ()
345+ valid , err := c .ValidAuthTokenCheck (log )
337346 if err != nil || ! valid {
338347 return nil , fmt .Errorf ("validity of the authentication token failed with error: %w" , err )
339348 }
@@ -348,7 +357,7 @@ func (c *Client) DoMultipartRequest(method, endpoint string, fields map[string]s
348357 }
349358
350359 // Construct URL using the ConstructAPIResourceEndpoint function
351- url := apiHandler .ConstructAPIResourceEndpoint (endpoint )
360+ url := apiHandler .ConstructAPIResourceEndpoint (endpoint , log )
352361
353362 // Create the request
354363 req , err := http .NewRequest (method , url , bytes .NewBuffer (requestData ))
@@ -362,41 +371,44 @@ func (c *Client) DoMultipartRequest(method, endpoint string, fields map[string]s
362371 req .Header .Set ("User-Agent" , GetUserAgentHeader ())
363372
364373 // Debug: Print request headers if in debug mode
365-
366- // Check if logging level is DEBUG or higher before logging headers
367- if log .GetLogLevel () <= LogLevelDebug {
368- // Debug: Print request headers without hiding sensitive information
369- LogHTTPHeaders (log , req .Header , false ) // Use false to display all headers
370- }
374+ headersStr := HeadersToString (req .Header )
375+ log .Debug ("HTTP Multipart Request Headers:" , zap .String ("headers" , headersStr ))
371376
372377 // Execute the request
373378 resp , err := c .httpClient .Do (req )
374379 if err != nil {
375380 log .Error ("Failed to send multipart request" ,
376- "method" , method ,
377- "endpoint" , endpoint ,
378- "error" , err .Error (),
381+ zap .String ("method" , method ),
382+ zap .String ("endpoint" , endpoint ),
383+ zap .Int ("status_code" , resp .StatusCode ),
384+ zap .String ("status_text" , http .StatusText (resp .StatusCode )),
379385 )
380386 return nil , err
381387 }
382388
383389 // Check for successful status code
384390 if resp .StatusCode < 200 || resp .StatusCode >= 300 {
391+ // Use HandleAPIError to process the error response and log it accordingly
392+ apiErr := errors .HandleAPIError (resp , log )
393+
394+ // Log additional context about the request that led to the error
385395 log .Error ("Received non-success status code from multipart request" ,
386- "method" , method ,
387- "endpoint" , endpoint ,
388- "status_code" , resp .StatusCode ,
389- "status_text" , http .StatusText (resp .StatusCode ),
396+ zap . String ( "method" , method ) ,
397+ zap . String ( "endpoint" , endpoint ) ,
398+ zap . Int ( "status_code" , resp .StatusCode ) ,
399+ zap . String ( "status_text" , http .StatusText (resp .StatusCode ) ),
390400 )
391- return resp , fmt .Errorf ("received non-success status code: %d - %s" , resp .StatusCode , http .StatusText (resp .StatusCode ))
401+
402+ // Return the original HTTP response and the API error
403+ return resp , apiErr
392404 }
393405
394406 // Unmarshal the response
395407 if err := apiHandler .UnmarshalResponse (resp , out , log ); err != nil {
396408 log .Error ("Failed to unmarshal HTTP response" ,
397- "method" , method ,
398- "endpoint" , endpoint ,
399- "error" , err .Error (),
409+ zap . String ( "method" , method ) ,
410+ zap . String ( "endpoint" , endpoint ) ,
411+ zap . String ( "error" , err .Error () ),
400412 )
401413 return resp , err
402414 }
0 commit comments