@@ -49,7 +49,9 @@ import (
4949
5050 _ "embed"
5151
52- "github.com/deploymenttheory/go-api-http-client/internal/httpclient"
52+ "github.com/deploymenttheory/go-api-http-client/internal/errors"
53+ "github.com/deploymenttheory/go-api-http-client/internal/logger"
54+ "go.uber.org/zap"
5355)
5456
5557// Endpoint constants represent the URL suffixes used for Jamf API token interactions.
@@ -104,13 +106,39 @@ type EndpointConfig struct {
104106
105107// JamfAPIHandler implements the APIHandler interface for the Jamf Pro API.
106108type JamfAPIHandler struct {
107- logger httpclient.Logger // logger is used to output logs for the API handling processes.
108- OverrideBaseDomain string // OverrideBaseDomain is used to override the base domain for URL construction.
109- InstanceName string // InstanceName is the name of the Jamf instance.
109+ OverrideBaseDomain string // OverrideBaseDomain is used to override the base domain for URL construction.
110+ InstanceName string // InstanceName is the name of the Jamf instance.
111+ }
112+
113+ type Logger interface {
114+ Debug (msg string , keysAndValues ... interface {})
115+ Info (msg string , keysAndValues ... interface {})
116+ Warn (msg string , keysAndValues ... interface {})
117+ Error (msg string , keysAndValues ... interface {})
110118}
111119
112120// Functions
113121
122+ func (j * JamfAPIHandler ) GetDefaultBaseDomain () string {
123+ return DefaultBaseDomain
124+ }
125+
126+ func (j * JamfAPIHandler ) GetOAuthTokenEndpoint () string {
127+ return OAuthTokenEndpoint
128+ }
129+
130+ func (j * JamfAPIHandler ) GetBearerTokenEndpoint () string {
131+ return BearerTokenEndpoint
132+ }
133+
134+ func (j * JamfAPIHandler ) GetTokenRefreshEndpoint () string {
135+ return TokenRefreshEndpoint
136+ }
137+
138+ func (j * JamfAPIHandler ) GetTokenInvalidateEndpoint () string {
139+ return TokenInvalidateEndpoint
140+ }
141+
114142// GetBaseDomain returns the appropriate base domain for URL construction.
115143// It uses OverrideBaseDomain if set, otherwise falls back to DefaultBaseDomain.
116144func (j * JamfAPIHandler ) GetBaseDomain () string {
@@ -120,19 +148,19 @@ func (j *JamfAPIHandler) GetBaseDomain() string {
120148 return DefaultBaseDomain
121149}
122150
123- // ConstructAPIResourceEndpoint returns the full URL for a Jamf API resource endpoint path.
124- func (j * JamfAPIHandler ) ConstructAPIResourceEndpoint (endpointPath string ) string {
151+ // ConstructAPIResourceEndpoint constructs the full URL for a Jamf API resource endpoint path and logs the URL .
152+ func (j * JamfAPIHandler ) ConstructAPIResourceEndpoint (endpointPath string , log logger. Logger ) string {
125153 baseDomain := j .GetBaseDomain ()
126154 url := fmt .Sprintf ("https://%s%s%s" , j .InstanceName , baseDomain , endpointPath )
127- j . logger . Info ("Request will be made to API URL: " , "URL" , url )
155+ log . Info ("Constructed API resource endpoint URL" , zap . String ( "URL" , url ) )
128156 return url
129157}
130158
131- // ConstructAPIAuthEndpoint returns the full URL for a Jamf API auth endpoint path.
132- func (j * JamfAPIHandler ) ConstructAPIAuthEndpoint (endpointPath string ) string {
159+ // ConstructAPIAuthEndpoint constructs the full URL for a Jamf API auth endpoint path and logs the URL .
160+ func (j * JamfAPIHandler ) ConstructAPIAuthEndpoint (endpointPath string , log logger. Logger ) string {
133161 baseDomain := j .GetBaseDomain ()
134162 url := fmt .Sprintf ("https://%s%s%s" , j .InstanceName , baseDomain , endpointPath )
135- j . logger . Info ("Request will be made to API authentication URL: " , "URL" , url )
163+ log . Info ("Constructed API authentication URL" , zap . String ( "URL" , url ) )
136164 return url
137165}
138166
@@ -144,36 +172,36 @@ func (j *JamfAPIHandler) ConstructAPIAuthEndpoint(endpointPath string) string {
144172// - For url endpoints starting with "/api", it defaults to "application/json" for the JamfPro API.
145173// If the endpoint does not match any of the predefined patterns, "application/json" is used as a fallback.
146174// This method logs the decision process at various stages for debugging purposes.
147- func (u * JamfAPIHandler ) GetContentTypeHeader (endpoint string ) string {
175+ func (u * JamfAPIHandler ) GetContentTypeHeader (endpoint string , log logger. Logger ) string {
148176 // Dynamic lookup from configuration should be the first priority
149177 for key , config := range configMap {
150178 if strings .HasPrefix (endpoint , key ) {
151179 if config .ContentType != nil {
152- u . logger . Debug ("Content-Type for endpoint found in configMap" , "endpoint" , endpoint , "content_type" , * config .ContentType )
180+ log . Debug ("Content-Type for endpoint found in configMap" , zap . String ( "endpoint" , endpoint ), zap . String ( "content_type" , * config .ContentType ) )
153181 return * config .ContentType
154182 }
155- u . logger . Debug ("Content-Type for endpoint is nil in configMap, handling as special case" , "endpoint" , endpoint )
183+ log . Debug ("Content-Type for endpoint is nil in configMap, handling as special case" , zap . String ( "endpoint" , endpoint ) )
156184 // If a nil ContentType is an expected case, do not set Content-Type header.
157185 return "" // Return empty to indicate no Content-Type should be set.
158186 }
159187 }
160188
161189 // If no specific configuration is found, then check for standard URL patterns.
162190 if strings .Contains (endpoint , "/JSSResource" ) {
163- u . logger . Debug ("Content-Type for endpoint defaulting to XML for Classic API" , "endpoint" , endpoint )
191+ log . Debug ("Content-Type for endpoint defaulting to XML for Classic API" , zap . String ( "endpoint" , endpoint ) )
164192 return "application/xml" // Classic API uses XML
165193 } else if strings .Contains (endpoint , "/api" ) {
166- u . logger . Debug ("Content-Type for endpoint defaulting to JSON for JamfPro API" , "endpoint" , endpoint )
194+ log . Debug ("Content-Type for endpoint defaulting to JSON for JamfPro API" , zap . String ( "endpoint" , endpoint ) )
167195 return "application/json" // JamfPro API uses JSON
168196 }
169197
170198 // Fallback to JSON if no other match is found.
171- u . logger . Debug ("Content-Type for endpoint not found in configMap or standard patterns, using default JSON" , "endpoint" , endpoint )
199+ log . Debug ("Content-Type for endpoint not found in configMap or standard patterns, using default JSON" , zap . String ( "endpoint" , endpoint ) )
172200 return "application/json"
173201}
174202
175203// MarshalRequest encodes the request body according to the endpoint for the API.
176- func (u * JamfAPIHandler ) MarshalRequest (body interface {}, method string , endpoint string ) ([]byte , error ) {
204+ func (u * JamfAPIHandler ) MarshalRequest (body interface {}, method string , endpoint string , log logger. Logger ) ([]byte , error ) {
177205 var (
178206 data []byte
179207 err error
@@ -195,47 +223,47 @@ func (u *JamfAPIHandler) MarshalRequest(body interface{}, method string, endpoin
195223 }
196224
197225 if method == "POST" || method == "PUT" {
198- u . logger . Trace ("XML Request Body: " , "Body" , string (data ))
226+ log . Debug ("XML Request Body" , zap . String ( "Body" , string (data ) ))
199227 }
200228
201229 case "json" :
202230 data , err = json .Marshal (body )
203231 if err != nil {
204- u . logger . Error ("Failed marshaling JSON request" , "error" , err )
232+ log . Error ("Failed marshaling JSON request" , zap . Error ( err ) )
205233 return nil , err
206234 }
207235
208236 if method == "POST" || method == "PUT" || method == "PATCH" {
209- u . logger . Debug ("JSON Request Body: " , string (data ))
237+ log . Debug ("JSON Request Body" , zap . String ( "Body" , string (data ) ))
210238 }
211239 }
212240
213241 return data , nil
214242}
215243
216244// UnmarshalResponse decodes the response body from XML or JSON format depending on the Content-Type header.
217- func (u * JamfAPIHandler ) UnmarshalResponse (resp * http.Response , out interface {}) error {
245+ func (u * JamfAPIHandler ) UnmarshalResponse (resp * http.Response , out interface {}, log logger. Logger ) error {
218246 // Handle DELETE method
219247 if resp .Request .Method == "DELETE" {
220248 if resp .StatusCode >= 200 && resp .StatusCode < 300 {
221249 return nil
222250 } else {
223- return fmt . Errorf ("DELETE request failed with status code: %d " , resp .StatusCode )
251+ return log . Error ("DELETE request failed" , zap . Int ( "Status Code " , resp .StatusCode ) )
224252 }
225253 }
226254
227255 bodyBytes , err := io .ReadAll (resp .Body )
228256 if err != nil {
229- u . logger . Error ("Failed reading response body" , "error" , err )
257+ log . Error ("Failed reading response body" , zap . Error ( err ) )
230258 return err
231259 }
232260
233261 // Log the raw response body and headers
234- u . logger . Trace ("Raw HTTP Response: " , string (bodyBytes ))
235- u . logger . Debug ("Unmarshaling response" , "status" , resp .Status )
262+ log . Debug ("Raw HTTP Response" , zap . String ( "Body" , string (bodyBytes ) ))
263+ log . Debug ("Unmarshaling response" , zap . String ( "status" , resp .Status ) )
236264
237265 // Log headers when in debug mode
238- u . logger . Debug ("HTTP Response Headers: " , resp .Header )
266+ log . Debug ("HTTP Response Headers" , zap . Any ( "Headers" , resp .Header ) )
239267
240268 // Check the Content-Type and Content-Disposition headers
241269 contentType := resp .Header .Get ("Content-Type" )
@@ -249,30 +277,38 @@ func (u *JamfAPIHandler) UnmarshalResponse(resp *http.Response, out interface{})
249277 // If content type is HTML, extract the error message
250278 if strings .Contains (contentType , "text/html" ) {
251279 errMsg := ExtractErrorMessageFromHTML (string (bodyBytes ))
252- u . logger . Warn ("Received HTML content" , "error_message" , errMsg , "status_code" , resp .StatusCode )
253- return & APIError {
280+ log . Warn ("Received HTML content" , zap . String ( "error_message" , errMsg ), zap . Int ( "status_code" , resp .StatusCode ) )
281+ return & errors. APIError {
254282 StatusCode : resp .StatusCode ,
255283 Message : errMsg ,
256284 }
257285 }
258286
259287 // Check for non-success status codes before attempting to unmarshal
260- if resp .StatusCode < 200 || resp .StatusCode >= 300 {
261- // Parse the error details from the response body for JSON content type
262- if strings .Contains (contentType , "application/json" ) {
288+ if resp .StatusCode < 200 || resp .StatusCode >= 300 {
289+ // Parse the error details from the response body for JSON content type
290+ if strings .Contains (contentType , "application/json" ) {
263291 description , err := ParseJSONErrorResponse (bodyBytes )
264292 if err != nil {
265- u .logger .Error ("Failed to parse JSON error response" , "error" , err )
266- return fmt .Errorf ("received non-success status code: %d and failed to parse error response" , resp .StatusCode )
293+ // Log the error using the structured logger and return the error
294+ return nil , c .Logger .Error ("Failed to parse JSON error response" ,
295+ zap .Error (err ),
296+ zap .Int ("status_code" , resp .StatusCode ),
297+ )
267298 }
268- return fmt .Errorf ("received non-success status code: %d, error: %s" , resp .StatusCode , description )
269- }
270-
271- // If the response is not JSON or another error occurs, return a generic error message
272- u .logger .Error ("Received non-success status code" , "status_code" , resp .StatusCode )
273- return fmt .Errorf ("received non-success status code: %d" , resp .StatusCode )
299+ // Log the error with description using the structured logger and return the error
300+ return nil , c .Logger .Error ("Received non-success status code with JSON response" ,
301+ zap .Int ("status_code" , resp .StatusCode ),
302+ zap .String ("error_description" , description ),
303+ )
274304 }
275305
306+ // If the response is not JSON or another error occurs, log a generic error message and return an error
307+ return nil , c .Logger .Error ("Received non-success status code without JSON response" ,
308+ zap .Int ("status_code" , resp .StatusCode ),
309+ )
310+ }
311+
276312 // Determine whether the content type is JSON or XML and unmarshal accordingly
277313 switch {
278314 case strings .Contains (contentType , "application/json" ):
@@ -285,20 +321,23 @@ func (u *JamfAPIHandler) UnmarshalResponse(resp *http.Response, out interface{})
285321 }
286322
287323 // Handle any errors that occurred during unmarshaling
288- if err != nil {
289- // If unmarshalling fails, check if the content might be HTML
290- if strings .Contains (string (bodyBytes ), "<html>" ) {
324+ if err != nil {
325+ // If unmarshalling fails, check if the content might be HTML
326+ if strings .Contains (string (bodyBytes ), "<html>" ) {
291327 errMsg := ExtractErrorMessageFromHTML (string (bodyBytes ))
292- u .logger .Warn ("Received HTML content instead of expected format" , "error_message" , errMsg , "status_code" , resp .StatusCode )
293- return fmt .Errorf (errMsg )
294- }
295-
296- // Log the error and return it
297- u .logger .Error ("Failed to unmarshal response" , "error" , err )
298- return fmt .Errorf ("failed to unmarshal response: %v" , err )
328+
329+ // Log the warning and return an error using the structured logger
330+ return nil , log .Warn ("Received HTML content instead of expected format" ,
331+ zap .String ("error_message" , errMsg ),
332+ zap .Int ("status_code" , resp .StatusCode ),
333+ )
299334 }
300335
301- return nil
336+ // Log the error using the structured logger and return the error
337+ return nil , log .Error ("Failed to unmarshal response" ,
338+ zap .Error (err ),
339+ )
340+
302341}
303342
304343// GetAcceptHeader constructs and returns a weighted Accept header string for HTTP requests.
@@ -327,7 +366,7 @@ func (u *JamfAPIHandler) GetAcceptHeader() string {
327366}
328367
329368// MarshalMultipartFormData takes a map with form fields and file paths and returns the encoded body and content type.
330- func (u * JamfAPIHandler ) MarshalMultipartRequest (fields map [string ]string , files map [string ]string ) ([]byte , string , error ) {
369+ func (u * JamfAPIHandler ) MarshalMultipartRequest (fields map [string ]string , files map [string ]string , log logger . Logger ) ([]byte , string , error ) {
331370 body := & bytes.Buffer {}
332371 writer := multipart .NewWriter (body )
333372
0 commit comments