Skip to content

Commit 5ae2210

Browse files
committed
Refactor APIHandler and Client struct***
***Update APIHandler interface and its implementations in APIHandler.go.*** ***Update Client struct and its methods in http_client.go.*** ***Update DoRequest and DoMultipartRequest methods in http_request.go.*** ***Add logger package and use logger.Logger interface in http_client.go.
1 parent 4e86b02 commit 5ae2210

File tree

5 files changed

+55
-53
lines changed

5 files changed

+55
-53
lines changed

internal/apihandlers/jamfpro/jamfpro_api_handler.go

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ import (
4848
"strings"
4949

5050
_ "embed"
51-
52-
"github.com/deploymenttheory/go-api-http-client/internal/httpclient"
5351
)
5452

5553
// Endpoint constants represent the URL suffixes used for Jamf API token interactions.
@@ -104,9 +102,15 @@ type EndpointConfig struct {
104102

105103
// JamfAPIHandler implements the APIHandler interface for the Jamf Pro API.
106104
type 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.
105+
OverrideBaseDomain string // OverrideBaseDomain is used to override the base domain for URL construction.
106+
InstanceName string // InstanceName is the name of the Jamf instance.
107+
}
108+
109+
type Logger interface {
110+
Debug(msg string, keysAndValues ...interface{})
111+
Info(msg string, keysAndValues ...interface{})
112+
Warn(msg string, keysAndValues ...interface{})
113+
Error(msg string, keysAndValues ...interface{})
110114
}
111115

112116
// Functions
@@ -121,18 +125,18 @@ func (j *JamfAPIHandler) GetBaseDomain() string {
121125
}
122126

123127
// ConstructAPIResourceEndpoint returns the full URL for a Jamf API resource endpoint path.
124-
func (j *JamfAPIHandler) ConstructAPIResourceEndpoint(endpointPath string) string {
128+
func (j *JamfAPIHandler) ConstructAPIResourceEndpoint(endpointPath string, logger Logger) string {
125129
baseDomain := j.GetBaseDomain()
126130
url := fmt.Sprintf("https://%s%s%s", j.InstanceName, baseDomain, endpointPath)
127-
j.logger.Info("Request will be made to API URL:", "URL", url)
131+
logger.Info("Request will be made to API URL:", "URL", url)
128132
return url
129133
}
130134

131135
// ConstructAPIAuthEndpoint returns the full URL for a Jamf API auth endpoint path.
132-
func (j *JamfAPIHandler) ConstructAPIAuthEndpoint(endpointPath string) string {
136+
func (j *JamfAPIHandler) ConstructAPIAuthEndpoint(endpointPath string, logger Logger) string {
133137
baseDomain := j.GetBaseDomain()
134138
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)
139+
logger.Info("Request will be made to API authentication URL:", "URL", url)
136140
return url
137141
}
138142

@@ -144,36 +148,36 @@ func (j *JamfAPIHandler) ConstructAPIAuthEndpoint(endpointPath string) string {
144148
// - For url endpoints starting with "/api", it defaults to "application/json" for the JamfPro API.
145149
// If the endpoint does not match any of the predefined patterns, "application/json" is used as a fallback.
146150
// This method logs the decision process at various stages for debugging purposes.
147-
func (u *JamfAPIHandler) GetContentTypeHeader(endpoint string) string {
151+
func (u *JamfAPIHandler) GetContentTypeHeader(endpoint string, logger Logger) string {
148152
// Dynamic lookup from configuration should be the first priority
149153
for key, config := range configMap {
150154
if strings.HasPrefix(endpoint, key) {
151155
if config.ContentType != nil {
152-
u.logger.Debug("Content-Type for endpoint found in configMap", "endpoint", endpoint, "content_type", *config.ContentType)
156+
logger.Debug("Content-Type for endpoint found in configMap", "endpoint", endpoint, "content_type", *config.ContentType)
153157
return *config.ContentType
154158
}
155-
u.logger.Debug("Content-Type for endpoint is nil in configMap, handling as special case", "endpoint", endpoint)
159+
logger.Debug("Content-Type for endpoint is nil in configMap, handling as special case", "endpoint", endpoint)
156160
// If a nil ContentType is an expected case, do not set Content-Type header.
157161
return "" // Return empty to indicate no Content-Type should be set.
158162
}
159163
}
160164

161165
// If no specific configuration is found, then check for standard URL patterns.
162166
if strings.Contains(endpoint, "/JSSResource") {
163-
u.logger.Debug("Content-Type for endpoint defaulting to XML for Classic API", "endpoint", endpoint)
167+
logger.Debug("Content-Type for endpoint defaulting to XML for Classic API", "endpoint", endpoint)
164168
return "application/xml" // Classic API uses XML
165169
} else if strings.Contains(endpoint, "/api") {
166-
u.logger.Debug("Content-Type for endpoint defaulting to JSON for JamfPro API", "endpoint", endpoint)
170+
logger.Debug("Content-Type for endpoint defaulting to JSON for JamfPro API", "endpoint", endpoint)
167171
return "application/json" // JamfPro API uses JSON
168172
}
169173

170174
// 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)
175+
logger.Debug("Content-Type for endpoint not found in configMap or standard patterns, using default JSON", "endpoint", endpoint)
172176
return "application/json"
173177
}
174178

175179
// 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) {
180+
func (u *JamfAPIHandler) MarshalRequest(body interface{}, method string, endpoint string, logger Logger) ([]byte, error) {
177181
var (
178182
data []byte
179183
err error
@@ -195,26 +199,26 @@ func (u *JamfAPIHandler) MarshalRequest(body interface{}, method string, endpoin
195199
}
196200

197201
if method == "POST" || method == "PUT" {
198-
u.logger.Trace("XML Request Body:", "Body", string(data))
202+
logger.Debug("XML Request Body:", "Body", string(data))
199203
}
200204

201205
case "json":
202206
data, err = json.Marshal(body)
203207
if err != nil {
204-
u.logger.Error("Failed marshaling JSON request", "error", err)
208+
logger.Error("Failed marshaling JSON request", "error", err)
205209
return nil, err
206210
}
207211

208212
if method == "POST" || method == "PUT" || method == "PATCH" {
209-
u.logger.Debug("JSON Request Body:", string(data))
213+
logger.Debug("JSON Request Body:", string(data))
210214
}
211215
}
212216

213217
return data, nil
214218
}
215219

216220
// 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 {
221+
func (u *JamfAPIHandler) UnmarshalResponse(resp *http.Response, out interface{}, logger Logger) error {
218222
// Handle DELETE method
219223
if resp.Request.Method == "DELETE" {
220224
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
@@ -226,16 +230,16 @@ func (u *JamfAPIHandler) UnmarshalResponse(resp *http.Response, out interface{})
226230

227231
bodyBytes, err := io.ReadAll(resp.Body)
228232
if err != nil {
229-
u.logger.Error("Failed reading response body", "error", err)
233+
logger.Error("Failed reading response body", "error", err)
230234
return err
231235
}
232236

233237
// 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)
238+
logger.Trace("Raw HTTP Response:", string(bodyBytes))
239+
logger.Debug("Unmarshaling response", "status", resp.Status)
236240

237241
// Log headers when in debug mode
238-
u.logger.Debug("HTTP Response Headers:", resp.Header)
242+
logger.Debug("HTTP Response Headers:", resp.Header)
239243

240244
// Check the Content-Type and Content-Disposition headers
241245
contentType := resp.Header.Get("Content-Type")
@@ -249,7 +253,7 @@ func (u *JamfAPIHandler) UnmarshalResponse(resp *http.Response, out interface{})
249253
// If content type is HTML, extract the error message
250254
if strings.Contains(contentType, "text/html") {
251255
errMsg := ExtractErrorMessageFromHTML(string(bodyBytes))
252-
u.logger.Warn("Received HTML content", "error_message", errMsg, "status_code", resp.StatusCode)
256+
logger.Warn("Received HTML content", "error_message", errMsg, "status_code", resp.StatusCode)
253257
return &APIError{
254258
StatusCode: resp.StatusCode,
255259
Message: errMsg,
@@ -289,12 +293,12 @@ func (u *JamfAPIHandler) UnmarshalResponse(resp *http.Response, out interface{})
289293
// If unmarshalling fails, check if the content might be HTML
290294
if strings.Contains(string(bodyBytes), "<html>") {
291295
errMsg := ExtractErrorMessageFromHTML(string(bodyBytes))
292-
u.logger.Warn("Received HTML content instead of expected format", "error_message", errMsg, "status_code", resp.StatusCode)
296+
logger.Warn("Received HTML content instead of expected format", "error_message", errMsg, "status_code", resp.StatusCode)
293297
return fmt.Errorf(errMsg)
294298
}
295299

296300
// Log the error and return it
297-
u.logger.Error("Failed to unmarshal response", "error", err)
301+
logger.Error("Failed to unmarshal response", "error", err)
298302
return fmt.Errorf("failed to unmarshal response: %v", err)
299303
}
300304

internal/httpclient/api_handler.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ import (
1313
type APIHandler interface {
1414
GetBaseDomain() string
1515
ConstructAPIResourceEndpoint(endpointPath string) string
16-
ConstructAPIAuthEndpoint(endpointPath string) string
17-
MarshalRequest(body interface{}, method string, endpoint string) ([]byte, error)
18-
MarshalMultipartRequest(fields map[string]string, files map[string]string) ([]byte, string, error) // New method for multipart
19-
UnmarshalResponse(resp *http.Response, out interface{}) error
20-
GetContentTypeHeader(method string) string
21-
GetAcceptHeader() string
16+
ConstructAPIAuthEndpoint(endpointPath string, logger Logger) string
17+
MarshalRequest(body interface{}, method string, endpoint string, logger Logger) ([]byte, error)
18+
MarshalMultipartRequest(fields map[string]string, files map[string]string, logger Logger) ([]byte, string, error)
19+
UnmarshalResponse(resp *http.Response, out interface{}, logger Logger) error
20+
GetContentTypeHeader(method string, logger Logger) string
21+
GetAcceptHeader(logger Logger) string
2222
}
2323

2424
// LoadAPIHandler returns an APIHandler based on the provided API type.

internal/httpclient/http_client.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"sync"
1414
"time"
1515

16+
"github.com/deploymenttheory/go-api-http-client/internal/logger"
1617
"go.uber.org/zap"
1718
)
1819

@@ -23,11 +24,10 @@ type Config struct {
2324
Auth AuthConfig // User can either supply these values manually or pass from LoadAuthConfig/Env vars
2425
APIType string `json:"apiType"`
2526
// Optional
26-
LogLevel LogLevel // Field for defining tiered logging level.
27-
MaxRetryAttempts int // Config item defines the max number of retry request attempts for retryable HTTP methods.
27+
LogLevel logger.LogLevel // Field for defining tiered logging level.
28+
MaxRetryAttempts int // Config item defines the max number of retry request attempts for retryable HTTP methods.
2829
EnableDynamicRateLimiting bool
29-
Logger Logger // Field for the packages initailzed logger
30-
MaxConcurrentRequests int // Field for defining the maximum number of concurrent requests allowed in the semaphore
30+
MaxConcurrentRequests int // Field for defining the maximum number of concurrent requests allowed in the semaphore
3131
TokenRefreshBufferPeriod time.Duration
3232
TotalRetryDuration time.Duration
3333
CustomTimeout time.Duration
@@ -67,15 +67,15 @@ type Client struct {
6767
httpClient *http.Client
6868
tokenLock sync.Mutex
6969
config Config
70-
logger Logger
70+
logger logger.Logger
7171
ConcurrencyMgr *ConcurrencyManager
7272
PerfMetrics PerformanceMetrics
7373
}
7474

7575
// BuildClient creates a new HTTP client with the provided configuration.
7676
func BuildClient(config Config) (*Client, error) {
7777
// Use the Logger interface type for the logger variable
78-
var logger Logger
78+
var logger logger.Logger
7979
if config.Logger == nil {
8080
logger = NewDefaultLogger()
8181
} else {

internal/httpclient/http_request.go

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,16 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}) (*htt
6767
ctx = context.WithValue(ctx, requestIDKey{}, requestID)
6868

6969
// Determine which set of encoding and content-type request rules to use
70-
//handler := GetAPIHandler(endpoint, c.config.LogLevel)
71-
handler := GetAPIHandler(c.config)
70+
apiHandler := c.APIHandler
7271

7372
// Marshal Request with correct encoding
74-
requestData, err := handler.MarshalRequest(body, method, endpoint)
73+
requestData, err := apiHandler.MarshalRequest(body, method, endpoint, c.logger)
7574
if err != nil {
7675
return nil, err
7776
}
7877

7978
// Construct URL using the ConstructAPIResourceEndpoint function
80-
url := c.ConstructAPIResourceEndpoint(endpoint)
79+
url := apiHandler.ConstructAPIResourceEndpoint(endpoint)
8180

8281
// Initialize total request counter
8382
c.PerfMetrics.lock.Lock()
@@ -91,9 +90,9 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}) (*htt
9190
}
9291

9392
// Define header content type based on url and http method
94-
contentType := handler.GetContentTypeHeader(endpoint)
93+
contentType := apiHandler.GetContentTypeHeader(endpoint, c.logger)
9594
// Define Request Headers dynamically based on handler logic
96-
acceptHeader := handler.GetAcceptHeader()
95+
acceptHeader := apiHandler.GetAcceptHeader(c.logger)
9796

9897
// Set Headers
9998
req.Header.Add("Authorization", "Bearer "+c.Token)
@@ -151,7 +150,7 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}) (*htt
151150
}
152151

153152
// Handle (unmarshal) response with API Handler
154-
if err := handler.UnmarshalResponse(resp, out); err != nil {
153+
if err := apiHandler.UnmarshalResponse(resp, out, c.logger); err != nil {
155154
switch e := err.(type) {
156155
case *APIError: // Assuming APIError is a type that includes StatusCode and Message
157156
// Log the API error with structured logging
@@ -258,7 +257,7 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}) (*htt
258257
CheckDeprecationHeader(resp, c.logger)
259258

260259
// Handle (unmarshal) response with API Handler
261-
if err := handler.UnmarshalResponse(resp, out); err != nil {
260+
if err := apiHandler.UnmarshalResponse(resp, out, c.logger); err != nil {
262261
switch e := err.(type) {
263262
case *APIError: // Assuming APIError is a type that includes StatusCode and Message
264263
// Log the API error with structured logging
@@ -338,17 +337,16 @@ func (c *Client) DoMultipartRequest(method, endpoint string, fields map[string]s
338337
}
339338

340339
// Determine which set of encoding and content-type request rules to use
341-
//handler := GetAPIHandler(endpoint, c.config.LogLevel)
342-
handler := GetAPIHandler(c.config)
340+
apiHandler := c.APIHandler
343341

344342
// Marshal the multipart form data
345-
requestData, contentType, err := handler.MarshalMultipartRequest(fields, files)
343+
requestData, contentType, err := apiHandler.MarshalMultipartRequest(fields, files, c.logger)
346344
if err != nil {
347345
return nil, err
348346
}
349347

350348
// Construct URL using the ConstructAPIResourceEndpoint function
351-
url := c.ConstructAPIResourceEndpoint(endpoint)
349+
url := apiHandler.ConstructAPIResourceEndpoint(endpoint)
352350

353351
// Create the request
354352
req, err := http.NewRequest(method, url, bytes.NewBuffer(requestData))
@@ -392,7 +390,7 @@ func (c *Client) DoMultipartRequest(method, endpoint string, fields map[string]s
392390
}
393391

394392
// Unmarshal the response
395-
if err := handler.UnmarshalResponse(resp, out); err != nil {
393+
if err := apiHandler.UnmarshalResponse(resp, out, c.logger); err != nil {
396394
c.logger.Error("Failed to unmarshal HTTP response",
397395
"method", method,
398396
"endpoint", endpoint,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package httpclient
1+
package logger
22

33
import (
44
"go.uber.org/zap"

0 commit comments

Comments
 (0)