Skip to content

Commit 0d8e4d0

Browse files
committed
Refactor APIHandler interface and LoadAPIHandler function
1 parent 5ae2210 commit 0d8e4d0

File tree

5 files changed

+254
-123
lines changed

5 files changed

+254
-123
lines changed

internal/apihandlers/jamfpro/jamfpro_api_handler.go

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

5050
_ "embed"
51+
52+
"github.com/deploymenttheory/go-api-http-client/internal/logger"
53+
"go.uber.org/zap"
5154
)
5255

5356
// Endpoint constants represent the URL suffixes used for Jamf API token interactions.
@@ -124,19 +127,19 @@ func (j *JamfAPIHandler) GetBaseDomain() string {
124127
return DefaultBaseDomain
125128
}
126129

127-
// ConstructAPIResourceEndpoint returns the full URL for a Jamf API resource endpoint path.
128-
func (j *JamfAPIHandler) ConstructAPIResourceEndpoint(endpointPath string, logger Logger) string {
130+
// ConstructAPIResourceEndpoint constructs the full URL for a Jamf API resource endpoint path and logs the URL.
131+
func (j *JamfAPIHandler) ConstructAPIResourceEndpoint(endpointPath string, log logger.Logger) string {
129132
baseDomain := j.GetBaseDomain()
130133
url := fmt.Sprintf("https://%s%s%s", j.InstanceName, baseDomain, endpointPath)
131-
logger.Info("Request will be made to API URL:", "URL", url)
134+
log.Info("Constructed API resource endpoint URL", zap.String("URL", url))
132135
return url
133136
}
134137

135-
// ConstructAPIAuthEndpoint returns the full URL for a Jamf API auth endpoint path.
136-
func (j *JamfAPIHandler) ConstructAPIAuthEndpoint(endpointPath string, logger Logger) string {
138+
// ConstructAPIAuthEndpoint constructs the full URL for a Jamf API auth endpoint path and logs the URL.
139+
func (j *JamfAPIHandler) ConstructAPIAuthEndpoint(endpointPath string, log logger.Logger) string {
137140
baseDomain := j.GetBaseDomain()
138141
url := fmt.Sprintf("https://%s%s%s", j.InstanceName, baseDomain, endpointPath)
139-
logger.Info("Request will be made to API authentication URL:", "URL", url)
142+
log.Info("Constructed API authentication URL", zap.String("URL", url))
140143
return url
141144
}
142145

@@ -148,36 +151,36 @@ func (j *JamfAPIHandler) ConstructAPIAuthEndpoint(endpointPath string, logger Lo
148151
// - For url endpoints starting with "/api", it defaults to "application/json" for the JamfPro API.
149152
// If the endpoint does not match any of the predefined patterns, "application/json" is used as a fallback.
150153
// This method logs the decision process at various stages for debugging purposes.
151-
func (u *JamfAPIHandler) GetContentTypeHeader(endpoint string, logger Logger) string {
154+
func (u *JamfAPIHandler) GetContentTypeHeader(endpoint string, log logger.Logger) string {
152155
// Dynamic lookup from configuration should be the first priority
153156
for key, config := range configMap {
154157
if strings.HasPrefix(endpoint, key) {
155158
if config.ContentType != nil {
156-
logger.Debug("Content-Type for endpoint found in configMap", "endpoint", endpoint, "content_type", *config.ContentType)
159+
log.Debug("Content-Type for endpoint found in configMap", zap.String("endpoint", endpoint), zap.String("content_type", *config.ContentType))
157160
return *config.ContentType
158161
}
159-
logger.Debug("Content-Type for endpoint is nil in configMap, handling as special case", "endpoint", endpoint)
162+
log.Debug("Content-Type for endpoint is nil in configMap, handling as special case", zap.String("endpoint", endpoint))
160163
// If a nil ContentType is an expected case, do not set Content-Type header.
161164
return "" // Return empty to indicate no Content-Type should be set.
162165
}
163166
}
164167

165168
// If no specific configuration is found, then check for standard URL patterns.
166169
if strings.Contains(endpoint, "/JSSResource") {
167-
logger.Debug("Content-Type for endpoint defaulting to XML for Classic API", "endpoint", endpoint)
170+
log.Debug("Content-Type for endpoint defaulting to XML for Classic API", zap.String("endpoint", endpoint))
168171
return "application/xml" // Classic API uses XML
169172
} else if strings.Contains(endpoint, "/api") {
170-
logger.Debug("Content-Type for endpoint defaulting to JSON for JamfPro API", "endpoint", endpoint)
173+
log.Debug("Content-Type for endpoint defaulting to JSON for JamfPro API", zap.String("endpoint", endpoint))
171174
return "application/json" // JamfPro API uses JSON
172175
}
173176

174177
// Fallback to JSON if no other match is found.
175-
logger.Debug("Content-Type for endpoint not found in configMap or standard patterns, using default JSON", "endpoint", endpoint)
178+
log.Debug("Content-Type for endpoint not found in configMap or standard patterns, using default JSON", zap.String("endpoint", endpoint))
176179
return "application/json"
177180
}
178181

179182
// MarshalRequest encodes the request body according to the endpoint for the API.
180-
func (u *JamfAPIHandler) MarshalRequest(body interface{}, method string, endpoint string, logger Logger) ([]byte, error) {
183+
func (u *JamfAPIHandler) MarshalRequest(body interface{}, method string, endpoint string, log logger.Logger) ([]byte, error) {
181184
var (
182185
data []byte
183186
err error
@@ -199,47 +202,47 @@ func (u *JamfAPIHandler) MarshalRequest(body interface{}, method string, endpoin
199202
}
200203

201204
if method == "POST" || method == "PUT" {
202-
logger.Debug("XML Request Body:", "Body", string(data))
205+
log.Debug("XML Request Body", zap.String("Body", string(data)))
203206
}
204207

205208
case "json":
206209
data, err = json.Marshal(body)
207210
if err != nil {
208-
logger.Error("Failed marshaling JSON request", "error", err)
211+
log.Error("Failed marshaling JSON request", zap.Error(err))
209212
return nil, err
210213
}
211214

212215
if method == "POST" || method == "PUT" || method == "PATCH" {
213-
logger.Debug("JSON Request Body:", string(data))
216+
log.Debug("JSON Request Body", zap.String("Body", string(data)))
214217
}
215218
}
216219

217220
return data, nil
218221
}
219222

220223
// UnmarshalResponse decodes the response body from XML or JSON format depending on the Content-Type header.
221-
func (u *JamfAPIHandler) UnmarshalResponse(resp *http.Response, out interface{}, logger Logger) error {
224+
func (u *JamfAPIHandler) UnmarshalResponse(resp *http.Response, out interface{}, log logger.Logger) error {
222225
// Handle DELETE method
223226
if resp.Request.Method == "DELETE" {
224227
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
225228
return nil
226229
} else {
227-
return fmt.Errorf("DELETE request failed with status code: %d", resp.StatusCode)
230+
return log.Error("DELETE request failed", zap.Int("Status Code", resp.StatusCode))
228231
}
229232
}
230233

231234
bodyBytes, err := io.ReadAll(resp.Body)
232235
if err != nil {
233-
logger.Error("Failed reading response body", "error", err)
236+
log.Error("Failed reading response body", zap.Error(err))
234237
return err
235238
}
236239

237240
// Log the raw response body and headers
238-
logger.Trace("Raw HTTP Response:", string(bodyBytes))
239-
logger.Debug("Unmarshaling response", "status", resp.Status)
241+
log.Debug("Raw HTTP Response", zap.String("Body", string(bodyBytes)))
242+
log.Debug("Unmarshaling response", zap.String("status", resp.Status))
240243

241244
// Log headers when in debug mode
242-
logger.Debug("HTTP Response Headers:", resp.Header)
245+
log.Debug("HTTP Response Headers", zap.Any("Headers", resp.Header))
243246

244247
// Check the Content-Type and Content-Disposition headers
245248
contentType := resp.Header.Get("Content-Type")
@@ -253,7 +256,7 @@ func (u *JamfAPIHandler) UnmarshalResponse(resp *http.Response, out interface{},
253256
// If content type is HTML, extract the error message
254257
if strings.Contains(contentType, "text/html") {
255258
errMsg := ExtractErrorMessageFromHTML(string(bodyBytes))
256-
logger.Warn("Received HTML content", "error_message", errMsg, "status_code", resp.StatusCode)
259+
log.Warn("Received HTML content", "error_message", errMsg, "status_code", resp.StatusCode)
257260
return &APIError{
258261
StatusCode: resp.StatusCode,
259262
Message: errMsg,
@@ -266,14 +269,14 @@ func (u *JamfAPIHandler) UnmarshalResponse(resp *http.Response, out interface{},
266269
if strings.Contains(contentType, "application/json") {
267270
description, err := ParseJSONErrorResponse(bodyBytes)
268271
if err != nil {
269-
u.logger.Error("Failed to parse JSON error response", "error", err)
272+
log.Error("Failed to parse JSON error response", "error", err)
270273
return fmt.Errorf("received non-success status code: %d and failed to parse error response", resp.StatusCode)
271274
}
272275
return fmt.Errorf("received non-success status code: %d, error: %s", resp.StatusCode, description)
273276
}
274277

275278
// If the response is not JSON or another error occurs, return a generic error message
276-
u.logger.Error("Received non-success status code", "status_code", resp.StatusCode)
279+
log.Error("Received non-success status code", "status_code", resp.StatusCode)
277280
return fmt.Errorf("received non-success status code: %d", resp.StatusCode)
278281
}
279282

@@ -293,12 +296,12 @@ func (u *JamfAPIHandler) UnmarshalResponse(resp *http.Response, out interface{},
293296
// If unmarshalling fails, check if the content might be HTML
294297
if strings.Contains(string(bodyBytes), "<html>") {
295298
errMsg := ExtractErrorMessageFromHTML(string(bodyBytes))
296-
logger.Warn("Received HTML content instead of expected format", "error_message", errMsg, "status_code", resp.StatusCode)
299+
log.Warn("Received HTML content instead of expected format", "error_message", errMsg, "status_code", resp.StatusCode)
297300
return fmt.Errorf(errMsg)
298301
}
299302

300303
// Log the error and return it
301-
logger.Error("Failed to unmarshal response", "error", err)
304+
log.Error("Failed to unmarshal response", "error", err)
302305
return fmt.Errorf("failed to unmarshal response: %v", err)
303306
}
304307

@@ -331,7 +334,7 @@ func (u *JamfAPIHandler) GetAcceptHeader() string {
331334
}
332335

333336
// MarshalMultipartFormData takes a map with form fields and file paths and returns the encoded body and content type.
334-
func (u *JamfAPIHandler) MarshalMultipartRequest(fields map[string]string, files map[string]string) ([]byte, string, error) {
337+
func (u *JamfAPIHandler) MarshalMultipartRequest(fields map[string]string, files map[string]string, log logger.Logger) ([]byte, string, error) {
335338
body := &bytes.Buffer{}
336339
writer := multipart.NewWriter(body)
337340

internal/httpclient/api_handler.go

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,46 @@
22
package httpclient
33

44
import (
5-
"fmt"
65
"net/http"
76

87
"github.com/deploymenttheory/go-api-http-client/internal/apihandlers/jamfpro"
8+
"github.com/deploymenttheory/go-api-http-client/internal/logger"
9+
"go.uber.org/zap"
910
)
1011

1112
// APIHandler is an interface for encoding, decoding, and determining content types for different API implementations.
1213
// It encapsulates behavior for encoding and decoding requests and responses.
1314
type APIHandler interface {
1415
GetBaseDomain() string
15-
ConstructAPIResourceEndpoint(endpointPath string) 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
16+
ConstructAPIResourceEndpoint(endpointPath string, log logger.Logger) string
17+
ConstructAPIAuthEndpoint(endpointPath string, log logger.Logger) string
18+
MarshalRequest(body interface{}, method string, endpoint string, log logger.Logger) ([]byte, error)
19+
MarshalMultipartRequest(fields map[string]string, files map[string]string, log logger.Logger) ([]byte, string, error)
20+
UnmarshalResponse(resp *http.Response, out interface{}, log logger.Logger) error
21+
GetContentTypeHeader(method string, log logger.Logger) string
22+
GetAcceptHeader() string
2223
}
2324

2425
// LoadAPIHandler returns an APIHandler based on the provided API type.
2526
// 'apiType' parameter could be "jamf" or "graph" to specify which API handler to load.
26-
func LoadAPIHandler(config Config, apiType string) (APIHandler, error) {
27+
func LoadAPIHandler(apiType string, log logger.Logger) (APIHandler, error) {
2728
var apiHandler APIHandler
2829
switch apiType {
2930
case "jamfpro":
30-
// Assuming GetAPIHandler returns a JamfAPIHandler
3131
apiHandler = &jamfpro.JamfAPIHandler{
3232
// Initialize with necessary parameters
3333
}
34+
log.Info("API handler loaded successfully", zap.String("APIType", apiType))
35+
3436
/*case "graph":
35-
// Assuming GetAPIHandler returns a GraphAPIHandler
3637
apiHandler = &graph.GraphAPIHandler{
37-
// Initialize with necessary parameters
38-
}*/
38+
// Initialize with necessary parameters
39+
}
40+
log.Info("API handler loaded successfully", zap.String("APIType", apiType))
41+
*/
3942
default:
40-
return nil, fmt.Errorf("unsupported API type: %s", apiType)
43+
return nil, log.Error("Unsupported API type", zap.String("APIType", apiType))
4144
}
4245

43-
// Set the logger level for the handler if needed
44-
logger := NewDefaultLogger() // Or use config.Logger if it's not nil
45-
logger.SetLevel(config.LogLevel)
46-
4746
return apiHandler, nil
4847
}

internal/httpclient/http_client.go

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ like the baseURL, authentication details, and an embedded standard HTTP client.
88
package httpclient
99

1010
import (
11-
"fmt"
1211
"net/http"
1312
"sync"
1413
"time"
@@ -67,75 +66,73 @@ type Client struct {
6766
httpClient *http.Client
6867
tokenLock sync.Mutex
6968
config Config
70-
logger logger.Logger
69+
Logger logger.Logger
7170
ConcurrencyMgr *ConcurrencyManager
7271
PerfMetrics PerformanceMetrics
7372
}
7473

7574
// BuildClient creates a new HTTP client with the provided configuration.
7675
func BuildClient(config Config) (*Client, error) {
77-
// Use the Logger interface type for the logger variable
78-
var logger logger.Logger
79-
if config.Logger == nil {
80-
logger = NewDefaultLogger()
81-
} else {
82-
logger = config.Logger
83-
}
76+
// Initialize the logger using the NewDefaultLogger function from the logger package.
77+
log := logger.NewDefaultLogger()
8478

85-
// Set the logger's level based on the provided configuration if present
86-
logger.SetLevel(config.LogLevel)
79+
// Set the logger's level based on the provided configuration.
80+
log.SetLevel(config.LogLevel)
8781

88-
// Validate LogLevel
89-
if config.LogLevel < LogLevelNone || config.LogLevel > LogLevelDebug {
90-
return nil, fmt.Errorf("invalid LogLevel setting: %d", config.LogLevel)
82+
if config.LogLevel < logger.LogLevelDebug || config.LogLevel > logger.LogLevelFatal {
83+
return nil, log.Error("Invalid LogLevel setting", zap.Int("Provided LogLevel", int(config.LogLevel)))
9184
}
9285

9386
// Use the APIType from the config to determine which API handler to load
94-
apiHandler, err := LoadAPIHandler(config, config.APIType)
87+
apiHandler, err := LoadAPIHandler(config.APIType, log)
9588
if err != nil {
96-
logger.Error("Failed to load API handler", zap.String("APIType", config.APIType), zap.Error(err))
97-
return nil, err // Return the original error without wrapping it in fmt.Errorf
89+
return nil, log.Error("Failed to load API handler", zap.String("APIType", config.APIType), zap.Error(err))
9890
}
9991

100-
logger.Info("Initializing new HTTP client", zap.String("InstanceName", config.InstanceName), zap.String("APIType", config.APIType), zap.Int("LogLevel", int(config.LogLevel)))
92+
log.Info("Initializing new HTTP client",
93+
zap.String("InstanceName", config.InstanceName), // Using zap.String for structured logging
94+
zap.String("APIType", config.APIType), // Using zap.String for structured logging
95+
zap.Int("LogLevel", int(config.LogLevel)), // Using zap.Int to log LogLevel as an integer
96+
)
97+
10198
// Validate and set default values for the configuration
10299
if config.InstanceName == "" {
103-
return nil, fmt.Errorf("instanceName cannot be empty")
100+
return nil, log.Error("InstanceName cannot be empty")
104101
}
105102

106103
if config.MaxRetryAttempts < 0 {
107104
config.MaxRetryAttempts = DefaultMaxRetryAttempts
108-
logger.Info("MaxRetryAttempts was negative, set to default value", zap.Int("MaxRetryAttempts", DefaultMaxRetryAttempts))
105+
log.Info("MaxRetryAttempts was negative, set to default value", zap.Int("MaxRetryAttempts", DefaultMaxRetryAttempts))
109106
}
110107

111108
if config.MaxConcurrentRequests <= 0 {
112109
config.MaxConcurrentRequests = DefaultMaxConcurrentRequests
113-
logger.Info("MaxConcurrentRequests was negative or zero, set to default value", zap.Int("MaxConcurrentRequests", DefaultMaxConcurrentRequests))
110+
log.Info("MaxConcurrentRequests was negative or zero, set to default value", zap.Int("MaxConcurrentRequests", DefaultMaxConcurrentRequests))
114111
}
115112

116113
if config.TokenRefreshBufferPeriod < 0 {
117114
config.TokenRefreshBufferPeriod = DefaultTokenBufferPeriod
118-
logger.Info("TokenRefreshBufferPeriod was negative, set to default value", zap.Duration("TokenRefreshBufferPeriod", DefaultTokenBufferPeriod))
115+
log.Info("TokenRefreshBufferPeriod was negative, set to default value", zap.Duration("TokenRefreshBufferPeriod", DefaultTokenBufferPeriod))
119116
}
120117

121118
if config.TotalRetryDuration <= 0 {
122119
config.TotalRetryDuration = DefaultTotalRetryDuration
123-
logger.Info("TotalRetryDuration was negative or zero, set to default value", zap.Duration("TotalRetryDuration", DefaultTotalRetryDuration))
120+
log.Info("TotalRetryDuration was negative or zero, set to default value", zap.Duration("TotalRetryDuration", DefaultTotalRetryDuration))
124121
}
125122

126123
if config.TokenRefreshBufferPeriod == 0 {
127124
config.TokenRefreshBufferPeriod = DefaultTokenBufferPeriod
128-
logger.Info("TokenRefreshBufferPeriod not set, set to default value", zap.Duration("TokenRefreshBufferPeriod", DefaultTokenBufferPeriod))
125+
log.Info("TokenRefreshBufferPeriod not set, set to default value", zap.Duration("TokenRefreshBufferPeriod", DefaultTokenBufferPeriod))
129126
}
130127

131128
if config.TotalRetryDuration == 0 {
132129
config.TotalRetryDuration = DefaultTotalRetryDuration
133-
logger.Info("TotalRetryDuration not set, set to default value", zap.Duration("TotalRetryDuration", DefaultTotalRetryDuration))
130+
log.Info("TotalRetryDuration not set, set to default value", zap.Duration("TotalRetryDuration", DefaultTotalRetryDuration))
134131
}
135132

136133
if config.CustomTimeout == 0 {
137134
config.CustomTimeout = DefaultTimeout
138-
logger.Info("CustomTimeout not set, set to default value", zap.Duration("CustomTimeout", DefaultTimeout))
135+
log.Info("CustomTimeout not set, set to default value", zap.Duration("CustomTimeout", DefaultTimeout))
139136
}
140137

141138
// Determine the authentication method
@@ -145,7 +142,7 @@ func BuildClient(config Config) (*Client, error) {
145142
} else if config.Auth.ClientID != "" && config.Auth.ClientSecret != "" {
146143
AuthMethod = "oauth"
147144
} else {
148-
return nil, fmt.Errorf("invalid AuthConfig")
145+
return nil, log.Error("Invalid AuthConfig", zap.String("Username", config.Auth.Username), zap.String("ClientID", config.Auth.ClientID))
149146
}
150147

151148
client := &Client{
@@ -154,21 +151,20 @@ func BuildClient(config Config) (*Client, error) {
154151
AuthMethod: AuthMethod,
155152
httpClient: &http.Client{Timeout: config.CustomTimeout},
156153
config: config,
157-
logger: logger,
158-
ConcurrencyMgr: NewConcurrencyManager(config.MaxConcurrentRequests, logger, true),
154+
Logger: log,
155+
ConcurrencyMgr: NewConcurrencyManager(config.MaxConcurrentRequests, log, true),
159156
PerfMetrics: PerformanceMetrics{},
160157
}
161158

162-
// Get auth token
159+
// Authenticate and check token validity.
163160
_, err = client.ValidAuthTokenCheck()
164161
if err != nil {
165-
logger.Error("Failed to validate or obtain auth token", zap.Error(err))
166-
return nil, fmt.Errorf("failed to validate auth: %w", err)
162+
return nil, log.Error("Failed to validate or obtain auth token", zap.Error(err))
167163
}
168164

169165
go client.StartMetricEvaluation()
170166

171-
logger.Info("New client initialized", zap.String("InstanceName", client.InstanceName), zap.String("AuthMethod", AuthMethod), zap.Int("MaxRetryAttempts", config.MaxRetryAttempts), zap.Int("MaxConcurrentRequests", config.MaxConcurrentRequests), zap.Bool("EnableDynamicRateLimiting", config.EnableDynamicRateLimiting))
167+
log.Info("New client initialized", zap.String("InstanceName", client.InstanceName), zap.String("AuthMethod", AuthMethod), zap.Int("MaxRetryAttempts", config.MaxRetryAttempts), zap.Int("MaxConcurrentRequests", config.MaxConcurrentRequests), zap.Bool("EnableDynamicRateLimiting", config.EnableDynamicRateLimiting))
172168

173169
return client, nil
174170

0 commit comments

Comments
 (0)