diff options
| -rw-r--r-- | err.go | 19 | ||||
| -rw-r--r-- | lulu.go | 51 | ||||
| -rw-r--r-- | print_test.go | 8 | ||||
| -rw-r--r-- | query.go | 45 |
4 files changed, 106 insertions, 17 deletions
@@ -18,6 +18,7 @@ func pkgErrf(err error, format string, a ...any) error { err) } +// error encoding request type errEncReq struct { payload any path string @@ -28,17 +29,19 @@ func (e errEncReq) Error() string { return fmt.Sprintf("error encoding request body %v for %s: %v", e.payload, e.path, e.error) } -type errResp struct { +// server responded with the wrong status +type errRespStatus struct { *http.Response } -func (e errResp) Error() string { +func (e errRespStatus) Error() string { resp := e.Response req := resp.Request body, _ := io.ReadAll(resp.Body) return fmt.Sprintf("%s %s: %s: %s", req.Method, req.URL, resp.Status, body) } +// error reading response type errReadResp struct { *http.Response error @@ -50,6 +53,7 @@ func (e errReadResp) Error() string { req.Method, req.URL, e.error) } +// error decoding response type errDecResp struct { *http.Response body []byte @@ -61,3 +65,14 @@ func (e errDecResp) Error() string { return fmt.Sprintf("%s %s: error decoding response body `%s`: %v", req.Method, req.URL, string(e.body), e.error) } + +// server returned a bad response +type errResp struct { + *http.Response + error +} + +func (e errResp) Error() string { + req := e.Response.Request + return fmt.Sprintf("%s %s: bad response: %v", req.Method, req.URL, e.error) +} @@ -24,7 +24,7 @@ const ( coverDimensionsPath = "/cover-dimensions" validateCoverPath = "/validate-cover" printJobCostPath = "/print-job-cost-calculations" - printJobPath = " /print-jobs" + printJobsPath = "/print-jobs" ) // ApiUrl is the location of the API server. It is set to the sandbox @@ -180,28 +180,63 @@ func (c *Client) PrintJobCost(items []PrintJobCostLineItem, addr ShippingAddress // // https://api.lulu.com/docs/#tag/Print-Jobs/operation/Print-Jobs_list func (c *Client) PrintJobs(queries ...PrintJobQuery) ([]PrintJob, error) { - //q := parsePrintJobQueries(queries) - //page := 1 + var q printJobQueries + q.apply(queries...) + qvals := q.vals() - // TODO - return nil, fmt.Errorf("not implemented") + verify := func(v any) error { + resp := v.(*getPrintJobsResp) + if int(resp.Count) != len(resp.Results) { + return fmt.Errorf("count (%d) != len(results) (%d)", resp.Count, len(resp.Results)) + } + return nil + } + + var jobs []PrintJob + for page := 1; ; page++ { + qvals.Set("page", fmt.Sprint(page)) + var resp getPrintJobsResp + if err := c.getQueryDecodeVerify(printJobsPath, qvals, &resp, verify); err != nil { + return jobs, pkgErr(err) + } + if len(resp.Results) > 0 { + jobs = append(jobs, resp.Results...) + } else { + return jobs, nil + } + } } // getDecode sends a GET request and unmarshals the response. func (c *Client) getDecode(path string, v any) error { + verify := func(v any) error { return nil } + return c.getQueryDecodeVerify(path, nil, v, verify) +} + +// getDecodeVerify sends a GET /path/?query request and unmarshals and +// verifies the response. +func (c *Client) getQueryDecodeVerify(path string, query url.Values, v any, verify func(any) error) error { url, err := url.JoinPath(ApiUrl, path) if err != nil { return err } + url += "?" + query.Encode() + resp, err := c.c.Get(url) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return errResp{resp} + return errRespStatus{resp} } - return decodeResponse(resp, v) + if err := decodeResponse(resp, v); err != nil { + return err + } + if err := verify(v); err != nil { + return errResp{resp, err} + } + return nil } // postDecode sends a POST request and unmarshals the response. @@ -212,7 +247,7 @@ func (c *Client) postDecode(path string, payload any, wantStatus int, v any) err } defer resp.Body.Close() if resp.StatusCode != wantStatus { - return errResp{resp} + return errRespStatus{resp} } return decodeResponse(resp, v) } diff --git a/print_test.go b/print_test.go index 9e51095..d14c529 100644 --- a/print_test.go +++ b/print_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/shopspring/decimal" + "github.com/stretchr/testify/require" ) //go:embed testdata/getprintjobsresp.json @@ -113,3 +114,10 @@ func TestUnmarshalGetPrintJobsResp(t *testing.T) { requireUnmarshalJsonEq(t, want, getPrintJobsRespJson) } + +func TestPrintJobs(t *testing.T) { + t.Fail() // TODO: create a few print jobs and retrieve them + c := newClient(t) + _, err := c.PrintJobs() + require.NoError(t, err) +} @@ -1,6 +1,12 @@ package lulu -import "time" +import ( + "fmt" + "net/url" + "time" +) + +type PrintJobQuery func(*printJobQueries) type printJobQueries struct { creatAfter, creatBefore, modAfter, modBefore *time.Time @@ -9,15 +15,40 @@ type printJobQueries struct { excludeLineItems bool } -func parsePrintJobQueries(qs []PrintJobQuery) printJobQueries { - var q printJobQueries - for i := range qs { - qs[i](&q) +func (q *printJobQueries) apply(f ...PrintJobQuery) { + for i := range f { + f[i](q) } - return q } -type PrintJobQuery func(*printJobQueries) +func (q *printJobQueries) vals() url.Values { + v := url.Values{} + if q.creatAfter != nil { + v.Set("created_after", q.creatAfter.UTC().Format(time.RFC3339)) + } + if q.creatBefore != nil { + v.Set("created_before", q.creatBefore.UTC().Format(time.RFC3339)) + } + if q.modAfter != nil { + v.Set("modified_after", q.modAfter.UTC().Format(time.RFC3339)) + } + if q.modBefore != nil { + v.Set("modified_before", q.modBefore.UTC().Format(time.RFC3339)) + } + if q.status != nil { + v.Set("status", string(*q.status)) + } + if q.id != nil { + v.Set("id", fmt.Sprint(*q.id)) + } + if q.orderId != nil { + v.Set("order_id", fmt.Sprint(*q.orderId)) + } + if q.excludeLineItems { + v.Set("exclude_line_items", "true") + } + return v +} // Include only print jobs created after t. func FilterCreatedAfter(t time.Time) PrintJobQuery { |