From a9523ce3e813288cc9b8346b4ba30427c95f86a3 Mon Sep 17 00:00:00 2001 From: Florian Kinder Date: Wed, 16 Jul 2025 00:25:51 +0900 Subject: [PATCH 1/2] Fix API compatibility issues and update response structures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add configurable userIdentifier field to Client (replaces hardcoded "MTS API") - Add QueueID support for Submit endpoint (printer ID and queue ID are separate) - Fix Submit endpoint to properly handle v1.1 API requirements: - Send version: 1.1 header for v1.1 features - Keep title, user, PDL in query params (not body) for v1.1 - Add userMapping and releaseImmediately parameter support - Update PrintOptions with v1.1 fields (MediaSize, Scaling) - Fix SubmitResponse structure to match actual API: - Change timestamps from int64 to ISO format strings - Add nested _links section within job object - Update root _links structure - Update Printer response structures for proper HAL+JSON parsing: - Fix Copies.Default field name (defaultz) - Add proper HAL link structures instead of generic maps - Update PrintFile and PrintData to accept separate queueID parameter Breaking changes: - PrintFile and PrintData now require queueID parameter - Submit requires QueueID to be set (no backward compatibility) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- client.go | 23 ++++-- print.go | 117 +++++++++++++++++++++-------- print_test.go | 200 +++++++++++++++++++++++++++----------------------- printer.go | 56 +++++++++----- 4 files changed, 246 insertions(+), 150 deletions(-) diff --git a/client.go b/client.go index c9b2d9a..68bbe03 100644 --- a/client.go +++ b/client.go @@ -17,7 +17,7 @@ const ( defaultBaseURL = "https://api.printix.net" defaultAuthURL = "https://auth.printix.net/oauth/token" testAuthURL = "https://auth.testenv.printix.net/oauth/token" - submitEndpoint = "/cloudprint/tenants/%s/printers/%s/jobs" + submitEndpoint = "/cloudprint/tenants/%s/printers/%s/queues/%s/submit" completeUploadEndpoint = "/cloudprint/completeUpload" printersEndpoint = "/cloudprint/tenants/%s/printers" jobsEndpoint = "/cloudprint/tenants/%s/jobs" @@ -38,6 +38,7 @@ type Client struct { testMode bool rateLimitRemain int rateLimitReset time.Time + userIdentifier string } // Option is a function that configures the client. @@ -79,14 +80,22 @@ func WithAuthURL(authURL string) Option { } } +// WithUserIdentifier sets the user identifier for print jobs. +func WithUserIdentifier(userIdentifier string) Option { + return func(c *Client) { + c.userIdentifier = userIdentifier + } +} + // New creates a new Printix client. func New(clientID, clientSecret string, opts ...Option) *Client { c := &Client{ - httpClient: &http.Client{Timeout: 30 * time.Second}, - baseURL: defaultBaseURL, - authURL: defaultAuthURL, - clientID: clientID, - clientSecret: clientSecret, + httpClient: &http.Client{Timeout: 30 * time.Second}, + baseURL: defaultBaseURL, + authURL: defaultAuthURL, + clientID: clientID, + clientSecret: clientSecret, + userIdentifier: "API Client", } for _, opt := range opts { @@ -179,7 +188,7 @@ func (c *Client) doRequestWithHeaders(ctx context.Context, method, endpoint stri if body != nil { req.Header.Set("Content-Type", "application/json") } - + // Add custom headers for key, value := range customHeaders { req.Header.Set(key, value) diff --git a/print.go b/print.go index 0e0d9d4..7c809d7 100644 --- a/print.go +++ b/print.go @@ -11,21 +11,31 @@ import ( "time" ) +// UserMapping represents user mapping for print job assignment. +type UserMapping struct { + Key string `json:"key"` // AzureObjectId, AzureUPN, SAMAccountName, OnPremImmutableId, OnPremUpn, Email + Value string `json:"value"` // Value to filter for +} + // PrintJob represents a print job submission. type PrintJob struct { PrinterID string `json:"-"` // Not sent in body, used in URL - Title string `json:"title,omitempty"` - User string `json:"user,omitempty"` - PDL string `json:"PDL,omitempty"` - // v1.1 properties - Color *bool `json:"color,omitempty"` - Duplex string `json:"duplex,omitempty"` // NONE, SHORT_EDGE, LONG_EDGE - PageOrientation string `json:"page_orientation,omitempty"` // PORTRAIT, LANDSCAPE, AUTO - Copies *int `json:"copies,omitempty"` - MediaSize string `json:"media_size,omitempty"` - Scaling string `json:"scaling,omitempty"` // NOSCALE, SHRINK, FIT - TestMode bool `json:"-"` // Not sent to API - UseV11 bool `json:"-"` // Use v1.1 API + QueueID string `json:"-"` // Not sent in body, used in URL + Title string `json:"-"` // Not sent in body, used in URL query + User string `json:"-"` // Not sent in body, used in URL query + PDL string `json:"-"` // Not sent in body, used in URL query + // v1.1 properties (sent in body) + Color *bool `json:"color,omitempty"` + Duplex string `json:"duplex,omitempty"` // NONE, SHORT_EDGE, LONG_EDGE + PageOrientation string `json:"page_orientation,omitempty"` // PORTRAIT, LANDSCAPE, AUTO + Copies *int `json:"copies,omitempty"` + MediaSize string `json:"media_size,omitempty"` + Scaling string `json:"scaling,omitempty"` // NOSCALE, SHRINK, FIT + UserMapping *UserMapping `json:"userMapping,omitempty"` + // Control fields + ReleaseImmediately *bool `json:"-"` // Not sent in body, used in URL query + TestMode bool `json:"-"` // Not sent in body, used in URL query + UseV11 bool `json:"-"` // Use v1.1 API } // SubmitResponse represents the response from submitting a print job. @@ -33,25 +43,38 @@ type SubmitResponse struct { Response Job struct { ID string `json:"id"` - CreateTime int64 `json:"createTime"` - UpdateTime int64 `json:"updateTime"` + CreateTime string `json:"createTime"` // ISO format timestamp + UpdateTime string `json:"updateTime"` // ISO format timestamp Status string `json:"status"` OwnerID string `json:"ownerId"` ContentType string `json:"contentType"` Title string `json:"title"` + Links struct { + Self struct { + Href string `json:"href"` + } `json:"self"` + Printer struct { + Href string `json:"href"` + } `json:"printer"` + ChangeOwner struct { + Href string `json:"href"` + Templated bool `json:"templated"` + } `json:"changeOwner"` + } `json:"_links"` } `json:"job"` UploadLinks []struct { URL string `json:"url"` Headers map[string]string `json:"headers"` - Type string `json:"type"` // "Azure" or "GCP" + Type string `json:"type,omitempty"` // "Azure" or "GCP" - not always present } `json:"uploadLinks"` Links struct { - Self struct { - Href string `json:"href"` - } `json:"self"` UploadCompleted struct { Href string `json:"href"` } `json:"uploadCompleted"` + ChangeOwner struct { + Href string `json:"href"` + Templated bool `json:"templated"` + } `json:"changeOwner"` } `json:"_links"` } @@ -62,11 +85,13 @@ type CompleteUploadRequest struct { // PrintOptions represents print job options. type PrintOptions struct { - Copies int `json:"copies,omitempty"` - Color bool `json:"color,omitempty"` - Duplex string `json:"duplex,omitempty"` // "none", "long-edge", "short-edge" - PageRange string `json:"pageRange,omitempty"` - Orientation string `json:"orientation,omitempty"` // "portrait", "landscape" + Copies int `json:"copies,omitempty"` // Number of copies (positive integer) + Color bool `json:"color,omitempty"` // true for color, false for monochrome + Duplex string `json:"duplex,omitempty"` // "none", "long-edge", "short-edge" + Orientation string `json:"orientation,omitempty"` // "portrait", "landscape" + MediaSize string `json:"mediaSize,omitempty"` // Paper size: A0-A5, B4-B5, LETTER, LEGAL, etc. + Scaling string `json:"scaling,omitempty"` // "NOSCALE", "SHRINK", "FIT" + PageRange string `json:"pageRange,omitempty"` // Page range (not used in v1.1 API) } // Submit creates a new print job. @@ -75,7 +100,10 @@ func (c *Client) Submit(ctx context.Context, job *PrintJob) (*SubmitResponse, er return nil, fmt.Errorf("tenant ID is required for job submission") } - endpoint := fmt.Sprintf(submitEndpoint, c.tenantID, job.PrinterID) + if job.QueueID == "" { + return nil, fmt.Errorf("queue ID is required for job submission") + } + endpoint := fmt.Sprintf(submitEndpoint, c.tenantID, job.PrinterID, job.QueueID) // Add query parameters params := url.Values{} @@ -91,6 +119,12 @@ func (c *Client) Submit(ctx context.Context, job *PrintJob) (*SubmitResponse, er if c.testMode || job.TestMode { params.Set("test", "true") } + // Handle releaseImmediately parameter (default is true) + if job.ReleaseImmediately != nil && !*job.ReleaseImmediately { + params.Set("releaseImmediately", "false") + } else if job.ReleaseImmediately == nil { + params.Set("releaseImmediately", "true") + } if len(params) > 0 { endpoint += "?" + params.Encode() @@ -103,7 +137,6 @@ func (c *Client) Submit(ctx context.Context, job *PrintJob) (*SubmitResponse, er if job.UseV11 || job.Color != nil || job.Duplex != "" || job.PageOrientation != "" || job.Copies != nil || job.MediaSize != "" || job.Scaling != "" { headers["version"] = "1.1" - headers["Content-Type"] = "application/json" // Build v1.1 request body v11Body := make(map[string]any) @@ -125,10 +158,14 @@ func (c *Client) Submit(ctx context.Context, job *PrintJob) (*SubmitResponse, er if job.Scaling != "" { v11Body["scaling"] = job.Scaling } - - if len(v11Body) > 0 { - requestBody = v11Body + if job.UserMapping != nil { + v11Body["userMapping"] = job.UserMapping + } else { + v11Body["userMapping"] = nil } + + // Always send body for v1.1, even if empty + requestBody = v11Body } resp, err := c.doRequestWithHeaders(ctx, http.MethodPost, endpoint, requestBody, headers) @@ -205,7 +242,7 @@ func (c *Client) CompleteUpload(ctx context.Context, completeURL string) error { } // PrintFile prints a file using Printix. -func (c *Client) PrintFile(ctx context.Context, printerID, title, filePath string, options *PrintOptions) error { +func (c *Client) PrintFile(ctx context.Context, printerID, queueID, title, filePath string, options *PrintOptions) error { // Read the file data, err := os.ReadFile(filePath) if err != nil { @@ -230,8 +267,9 @@ func (c *Client) PrintFile(ctx context.Context, printerID, title, filePath strin // Create print job job := &PrintJob{ PrinterID: printerID, + QueueID: queueID, Title: title, - User: "MTS API", + User: c.userIdentifier, PDL: pdl, TestMode: c.testMode, } @@ -261,6 +299,13 @@ func (c *Client) PrintFile(ctx context.Context, printerID, title, filePath strin case "landscape": job.PageOrientation = "LANDSCAPE" } + // Add new v1.1 options + if options.MediaSize != "" { + job.MediaSize = options.MediaSize + } + if options.Scaling != "" { + job.Scaling = options.Scaling + } } // Submit the job @@ -288,12 +333,13 @@ func (c *Client) PrintFile(ctx context.Context, printerID, title, filePath strin } // PrintData prints raw data using Printix. -func (c *Client) PrintData(ctx context.Context, printerID, title string, data []byte, pdl string, options *PrintOptions) error { +func (c *Client) PrintData(ctx context.Context, printerID, queueID, title string, data []byte, pdl string, options *PrintOptions) error { // Create print job job := &PrintJob{ PrinterID: printerID, + QueueID: queueID, Title: title, - User: "MTS API", + User: c.userIdentifier, PDL: pdl, TestMode: c.testMode, } @@ -323,6 +369,13 @@ func (c *Client) PrintData(ctx context.Context, printerID, title string, data [] case "landscape": job.PageOrientation = "LANDSCAPE" } + // Add new v1.1 options + if options.MediaSize != "" { + job.MediaSize = options.MediaSize + } + if options.Scaling != "" { + job.Scaling = options.Scaling + } } // Submit the job diff --git a/print_test.go b/print_test.go index 2f3b470..db2aa72 100644 --- a/print_test.go +++ b/print_test.go @@ -16,7 +16,8 @@ func TestClient_Submit(t *testing.T) { name string job *PrintJob setupServer func() *httptest.Server - want *SubmitResponse + wantJobID string + wantSuccess bool wantErr bool errContains string }{ @@ -24,6 +25,7 @@ func TestClient_Submit(t *testing.T) { name: "successful submission", job: &PrintJob{ PrinterID: "printer-123", + QueueID: "printer-123", Title: "Test Document", User: "Test", }, @@ -36,7 +38,7 @@ func TestClient_Submit(t *testing.T) { "access_token": "test-token", "expires_in": 3600, }) - case "/cloudprint/tenants/test-tenant/printers/printer-123/jobs": + case "/cloudprint/tenants/test-tenant/printers/printer-123/queues/printer-123/submit": // Check query parameters instead of body for v1.0 API assert.Equal(t, "Test Document", r.URL.Query().Get("title")) assert.Equal(t, "Test", r.URL.Query().Get("user")) @@ -45,12 +47,17 @@ func TestClient_Submit(t *testing.T) { "success": true, "job": map[string]interface{}{ "id": "job-456", - "createTime": 1600344674, - "updateTime": 1600344674, + "createTime": "2025-07-15T15:02:13.141525320Z", + "updateTime": "2025-07-15T15:02:13.248Z", "status": "Created", "ownerId": "owner-123", "contentType": "application/pdf", "title": "Test Document", + "_links": map[string]interface{}{ + "self": map[string]interface{}{ + "href": server.URL + "/cloudprint/tenants/test-tenant/jobs/job-456", + }, + }, }, "uploadLinks": []map[string]interface{}{ { @@ -69,53 +76,15 @@ func TestClient_Submit(t *testing.T) { })) return server }, - want: &SubmitResponse{ - Response: Response{Success: true}, - Job: struct { - ID string `json:"id"` - CreateTime int64 `json:"createTime"` - UpdateTime int64 `json:"updateTime"` - Status string `json:"status"` - OwnerID string `json:"ownerId"` - ContentType string `json:"contentType"` - Title string `json:"title"` - }{ - ID: "job-456", - Title: "Test Document", - Status: "Created", - }, - UploadLinks: []struct { - URL string `json:"url"` - Headers map[string]string `json:"headers"` - Type string `json:"type"` - }{ - { - URL: "https://storage.example.com/upload", - Headers: map[string]string{}, - Type: "Azure", - }, - }, - Links: struct { - Self struct { - Href string `json:"href"` - } `json:"self"` - UploadCompleted struct { - Href string `json:"href"` - } `json:"uploadCompleted"` - }{ - UploadCompleted: struct { - Href string `json:"href"` - }{ - Href: "", // Will be set dynamically - }, - }, - }, + wantJobID: "job-456", + wantSuccess: true, wantErr: false, }, { name: "submission with test mode", job: &PrintJob{ PrinterID: "printer-123", + QueueID: "printer-123", Title: "Test Document", TestMode: true, }, @@ -128,19 +97,24 @@ func TestClient_Submit(t *testing.T) { "access_token": "test-token", "expires_in": 3600, }) - case "/cloudprint/tenants/test-tenant/printers/printer-123/jobs": + case "/cloudprint/tenants/test-tenant/printers/printer-123/queues/printer-123/submit": assert.Equal(t, "true", r.URL.Query().Get("test")) _ = json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, "job": map[string]interface{}{ "id": "test-job-789", - "createTime": 1600344674, - "updateTime": 1600344674, + "createTime": "2025-07-15T15:02:13.141525320Z", + "updateTime": "2025-07-15T15:02:13.248Z", "status": "Created", "ownerId": "owner-123", "contentType": "application/pdf", "title": "Test Document", + "_links": map[string]interface{}{ + "self": map[string]interface{}{ + "href": server.URL + "/cloudprint/tenants/test-tenant/jobs/test-job-789", + }, + }, }, "uploadLinks": []map[string]interface{}{ { @@ -159,53 +133,91 @@ func TestClient_Submit(t *testing.T) { })) return server }, - want: &SubmitResponse{ - Response: Response{Success: true}, - Job: struct { - ID string `json:"id"` - CreateTime int64 `json:"createTime"` - UpdateTime int64 `json:"updateTime"` - Status string `json:"status"` - OwnerID string `json:"ownerId"` - ContentType string `json:"contentType"` - Title string `json:"title"` - }{ - ID: "test-job-789", - Title: "Test Document", - Status: "Created", - }, - UploadLinks: []struct { - URL string `json:"url"` - Headers map[string]string `json:"headers"` - Type string `json:"type"` - }{ - { - URL: "https://test.storage.example.com/upload", - Headers: map[string]string{}, - Type: "Azure", - }, - }, - Links: struct { - Self struct { - Href string `json:"href"` - } `json:"self"` - UploadCompleted struct { - Href string `json:"href"` - } `json:"uploadCompleted"` - }{ - UploadCompleted: struct { - Href string `json:"href"` - }{ - Href: "", // Will be set dynamically - }, - }, + wantJobID: "test-job-789", + wantSuccess: true, + wantErr: false, + }, + { + name: "submission with v1.1 API", + job: &PrintJob{ + PrinterID: "printer-123", + QueueID: "printer-123", + Title: "Test Document", + User: "Test", + UseV11: true, + Color: &[]bool{false}[0], + Duplex: "NONE", + Copies: &[]int{2}[0], + }, + setupServer: func() *httptest.Server { + var server *httptest.Server + server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/oauth/token": + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "access_token": "test-token", + "expires_in": 3600, + }) + case "/cloudprint/tenants/test-tenant/printers/printer-123/queues/printer-123/submit": + // Check v1.1 specific requirements + assert.Equal(t, "1.1", r.Header.Get("version")) + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + + // Check query parameters + assert.Equal(t, "Test Document", r.URL.Query().Get("title")) + assert.Equal(t, "Test", r.URL.Query().Get("user")) + assert.Equal(t, "true", r.URL.Query().Get("releaseImmediately")) + + // Check request body + var body map[string]interface{} + _ = json.NewDecoder(r.Body).Decode(&body) + assert.Equal(t, false, body["color"]) + assert.Equal(t, "NONE", body["duplex"]) + assert.Equal(t, float64(2), body["copies"]) + assert.Equal(t, nil, body["userMapping"]) + + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "success": true, + "job": map[string]interface{}{ + "id": "v11-job-123", + "createTime": "2025-07-15T15:02:13.141525320Z", + "updateTime": "2025-07-15T15:02:13.248Z", + "status": "Created", + "ownerId": "owner-123", + "contentType": "application/pdf", + "title": "Test Document", + "_links": map[string]interface{}{ + "self": map[string]interface{}{ + "href": server.URL + "/cloudprint/tenants/test-tenant/jobs/v11-job-123", + }, + }, + }, + "uploadLinks": []map[string]interface{}{ + { + "url": "https://storage.example.com/upload", + "headers": map[string]string{}, + "type": "Azure", + }, + }, + "_links": map[string]interface{}{ + "uploadCompleted": map[string]interface{}{ + "href": server.URL + "/cloudprint/jobs/v11-job-123/uploadCompleted", + }, + }, + }) + } + })) + return server }, + wantJobID: "v11-job-123", + wantSuccess: true, wantErr: false, }, { name: "submission failure", job: &PrintJob{ PrinterID: "printer-123", + QueueID: "printer-123", Title: "Test Document", }, setupServer: func() *httptest.Server { @@ -216,7 +228,7 @@ func TestClient_Submit(t *testing.T) { "access_token": "test-token", "expires_in": 3600, }) - case "/cloudprint/tenants/test-tenant/printers/printer-123/jobs": + case "/cloudprint/tenants/test-tenant/printers/printer-123/queues/printer-123/submit": _ = json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "errorDescription": "Printer not found", @@ -243,11 +255,13 @@ func TestClient_Submit(t *testing.T) { assert.Contains(t, err.Error(), tt.errContains) } else { require.NoError(t, err) - assert.Equal(t, tt.want.Job.ID, got.Job.ID) - assert.Equal(t, len(tt.want.UploadLinks), len(got.UploadLinks)) + assert.Equal(t, tt.wantSuccess, got.Success) + assert.Equal(t, tt.wantJobID, got.Job.ID) + assert.NotEmpty(t, got.Job.Title) + assert.NotEmpty(t, got.Job.Status) + assert.Greater(t, len(got.UploadLinks), 0) if len(got.UploadLinks) > 0 { - assert.Equal(t, tt.want.UploadLinks[0].URL, got.UploadLinks[0].URL) - assert.Equal(t, tt.want.UploadLinks[0].Type, got.UploadLinks[0].Type) + assert.NotEmpty(t, got.UploadLinks[0].URL) } // Just check that upload completed href is not empty assert.NotEmpty(t, got.Links.UploadCompleted.Href) diff --git a/printer.go b/printer.go index 4c5ca17..bab988a 100644 --- a/printer.go +++ b/printer.go @@ -10,16 +10,16 @@ import ( // Printer represents a Printix printer. type Printer struct { - ID string `json:"id"` - Name string `json:"name"` - ConnectionStatus string `json:"connectionStatus,omitempty"` - PrinterSignID string `json:"printerSignId,omitempty"` - Location string `json:"location,omitempty"` - Model string `json:"model,omitempty"` - Vendor string `json:"vendor,omitempty"` - SerialNo string `json:"serialNo,omitempty"` - Capabilities PrinterCapabilities `json:"capabilities,omitempty"` - Links map[string]interface{} `json:"_links,omitempty"` + ID string `json:"id"` + Name string `json:"name"` + ConnectionStatus string `json:"connectionStatus,omitempty"` + PrinterSignID string `json:"printerSignId,omitempty"` + Location string `json:"location,omitempty"` + Model string `json:"model,omitempty"` + Vendor string `json:"vendor,omitempty"` + SerialNo string `json:"serialNo,omitempty"` + Capabilities PrinterCapabilities `json:"capabilities,omitempty"` + Links PrinterLinks `json:"_links,omitempty"` } // PrinterCapabilities represents printer capabilities. @@ -30,7 +30,7 @@ type PrinterCapabilities struct { } `json:"media_size,omitempty"` SupportedContentType []ContentType `json:"supported_content_type,omitempty"` Copies struct { - Default int `json:"default,omitempty"` + Default int `json:"defaultz,omitempty"` Max int `json:"max,omitempty"` } `json:"copies,omitempty"` Color struct { @@ -76,12 +76,25 @@ type LocalizedString struct { Value string `json:"value"` } +// PrinterLinks represents HAL links for a printer. +type PrinterLinks struct { + Self Link `json:"self"` + Submit Link `json:"submit"` + Jobs Link `json:"jobs"` +} + +// Link represents a HAL link. +type Link struct { + Href string `json:"href"` + Templated bool `json:"templated,omitempty"` +} + // PrintersResponse represents the HAL+JSON response from listing printers. type PrintersResponse struct { - Links map[string]interface{} `json:"_links"` - Success bool `json:"success"` - Message string `json:"message"` - Printers []Printer `json:"printers"` + Links PrintersResponseLinks `json:"_links"` + Success bool `json:"success"` + Message string `json:"message"` + Printers []Printer `json:"printers"` Page struct { Size int `json:"size"` TotalElements int `json:"totalElements"` @@ -90,6 +103,13 @@ type PrintersResponse struct { } `json:"page"` } +// PrintersResponseLinks represents HAL links for printers response. +type PrintersResponseLinks struct { + Self Link `json:"self"` + Next *Link `json:"next,omitempty"` + Prev *Link `json:"prev,omitempty"` +} + // GetPrintersOptions represents options for listing printers. type GetPrintersOptions struct { Query string // Search query for printer names @@ -183,9 +203,9 @@ func (c *Client) GetPrinter(ctx context.Context, printerID string) (*Printer, er } var printerResp struct { - Links map[string]interface{} `json:"_links"` - Success bool `json:"success"` - Message string `json:"message"` + Links PrinterLinks `json:"_links"` + Success bool `json:"success"` + Message string `json:"message"` Printer } From 9a65ccef9364a1ce770b3a7bb5bd8b29fbc3af79 Mon Sep 17 00:00:00 2001 From: Florian Kinder Date: Wed, 16 Jul 2025 16:39:25 +0900 Subject: [PATCH 2/2] Use configured HTTP client for document uploads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove separate HTTP client creation in UploadDocument - Use the same httpClient configured when creating the client - This respects custom timeouts, transports, and other configurations - Enables better testing and consistency 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- print.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/print.go b/print.go index 7c809d7..026983a 100644 --- a/print.go +++ b/print.go @@ -8,7 +8,6 @@ import ( "net/http" "net/url" "os" - "time" ) // UserMapping represents user mapping for print job assignment. @@ -200,9 +199,8 @@ func (c *Client) UploadDocument(ctx context.Context, uploadLink string, headers req.Header.Set(k, v) } - // Use a separate HTTP client for cloud storage (no auth needed) - storageClient := &http.Client{Timeout: 60 * time.Second} - resp, err := storageClient.Do(req) + // Use the configured HTTP client for cloud storage uploads + resp, err := c.httpClient.Do(req) if err != nil { return fmt.Errorf("uploading document: %w", err) }