aboutsummaryrefslogtreecommitdiffstats
path: root/lulu.go
diff options
context:
space:
mode:
authorSam Anthony <sam@samanthony.xyz>2026-07-29 16:45:00 -0230
committerSam Anthony <sam@samanthony.xyz>2026-07-29 16:45:00 -0230
commite7bba51bb8bb41c462873c613bc6d4cf402a58d1 (patch)
treea94f91656e52aaf5c8edb5aed02679bae8efab11 /lulu.go
parent95acc996fdcc6221741a3072633c5753e88d0f07 (diff)
downloadlulu-e7bba51bb8bb41c462873c613bc6d4cf402a58d1.zip
sandbox and production client constructors
Previously this was a package-level configuration with Sandbox() and Production() controlling the global ApiUrl variable. Now it's controlled per-client.
Diffstat (limited to 'lulu.go')
-rw-r--r--lulu.go93
1 files changed, 46 insertions, 47 deletions
diff --git a/lulu.go b/lulu.go
index c3838dc..f79ebe0 100644
--- a/lulu.go
+++ b/lulu.go
@@ -15,11 +15,11 @@ import (
)
const (
- SandboxUrl = "https://api.sandbox.lulu.com/"
- ProductionUrl = "https://api.lulu.com/"
+ sandbox = "https://api.sandbox.lulu.com/"
+ production = "https://api.lulu.com/"
- ProductionApiKeyPage = "https://developers.lulu.com/user-profile/api-keys"
- SandboxApiKeyPage = "https://developers.sandbox.lulu.com/user-profile/api-keys"
+ productionKeyPage = "https://developers.lulu.com/user-profile/api-keys"
+ sandboxKeyPage = "https://developers.sandbox.lulu.com/user-profile/api-keys"
PollPeriod = time.Second
@@ -31,31 +31,11 @@ const (
printJobsPath = "/print-jobs"
)
-// ApiUrl is the location of the API server. It is set to the sandbox
-// environment by default; change it to the production environment when
-// you are ready to deploy.
-var ApiUrl = SandboxUrl
-
-// Sandbox sets ApiUrl to SandboxApiUrl so that subsequent requests will
-// be sent to the sandbox API server.
-func Sandbox() { ApiUrl = SandboxUrl }
-
-// Production sets ApiUrl to ProductionApiUrl so that subsequent requests
-// will be sent to the production API server.
-func Production() { ApiUrl = ProductionUrl }
-
-// ApiKeyPage returns the URL of the page where you can generate a
-// client-key and client-secret to use for authentication.
-func ApiKeyPage() string {
- if ApiUrl == SandboxUrl {
- return SandboxApiKeyPage
- }
- return ProductionApiKeyPage
-}
-
type Client struct {
- ctx context.Context
- c *http.Client
+ ctx context.Context
+ c *http.Client
+ apiUrl string
+ keyPageUrl string
}
// Credentials contains the client-key and client-secret used to
@@ -64,10 +44,23 @@ type Credentials struct {
Key, Secret string
}
-// NewClient returns a client that will use the given credentials to
-// connect to the API server.
+// NewClient creates a client that will use creds to connect to the
+// production API server.
+//
+// ctx is used by the OAuth2 client. If you don't care about this, use
+// [context.Background]. See [clientcredentials.Config.Client].
func NewClient(ctx context.Context, creds Credentials) (*Client, error) {
- tokenUrl, err := url.JoinPath(ApiUrl, tokenPath)
+ return newClient(ctx, creds, production, productionKeyPage)
+}
+
+// NewClient creates a client that will connect to the sandbox API
+// server. See [NewClient].
+func NewSandboxClient(ctx context.Context, creds Credentials) (*Client, error) {
+ return newClient(ctx, creds, sandbox, sandboxKeyPage)
+}
+
+func newClient(ctx context.Context, creds Credentials, apiUrl, keyPageUrl string) (*Client, error) {
+ tokenUrl, err := url.JoinPath(apiUrl, tokenPath)
if err != nil {
return nil, pkgErrf(err, "error creating client")
}
@@ -77,7 +70,7 @@ func NewClient(ctx context.Context, creds Credentials) (*Client, error) {
ClientSecret: creds.Secret,
TokenURL: tokenUrl,
}
- return &Client{ctx, cfg.Client(ctx)}, nil
+ return &Client{ctx, cfg.Client(ctx), apiUrl, keyPageUrl}, nil
}
// Context returns the client's context.
@@ -85,8 +78,8 @@ func (c *Client) Context() context.Context { return c.ctx }
// ValidateInterior starts a server-side validation job for the given
// interior file and polls its status until it finishes or the context
-// expires. See also: StartInteriorValidation() and
-// GetInteriorValidation().
+// expires. See [Client.StartInteriorValidation], [Client.GetInteriorValidation],
+// and [PollPeriod].
func (c *Client) ValidateInterior(ctx context.Context, srcUrl string, mfg PkgId) (InteriorValidation, error) {
id, err := c.StartInteriorValidation(srcUrl, mfg)
if err != nil {
@@ -95,7 +88,7 @@ func (c *Client) ValidateInterior(ctx context.Context, srcUrl string, mfg PkgId)
return c.pollInteriorValidation(ctx, id, InteriorStatusNormalized)
}
-// ValidateInteriorBasic is like ValidateInterior but without the
+// ValidateInteriorBasic is like [Client.ValidateInterior] but without the
// optional pod_package_id.
func (c *Client) ValidateInteriorBasic(ctx context.Context, srcUrl string) (InteriorValidation, error) {
id, err := c.StartInteriorValidationBasic(srcUrl)
@@ -116,7 +109,7 @@ func (c *Client) pollInteriorValidation(ctx context.Context, id uint, wantStatus
// StartInteriorValidation starts a server-side validation job for the
// interior file located at srcUrl using manufacturing settings given by
-// mfg. It returns the ID of the job. Use GetInteriorValidation() to poll
+// mfg. It returns the ID of the job. Use [Client.GetInteriorValidation] to poll
// the status of the job.
//
// https://api.lulu.com/docs/#tag/Files-validation/operation/Validate-Interior_create
@@ -128,7 +121,7 @@ func (c *Client) StartInteriorValidation(srcUrl string, mfg PkgId) (uint, error)
return id, nil
}
-// StartInteriorValidationBasic is like StartInteriorValidation but
+// StartInteriorValidationBasic is like [Client.StartInteriorValidation] but
// without the optional pod_package_id.
//
// https://api.lulu.com/docs/#tag/Files-validation/operation/Validate-Interior_create
@@ -177,9 +170,10 @@ func (c *Client) CoverDimensions(mfg PkgId, npages uint, unit Unit) (CoverDimens
return dims, nil
}
-// ValidateCover starts a server-side validation job for the given cover
-// file and polls its status until it finishes or the context expires.
-// See also: StartCoverValidation() and GetCoverValidation().
+// ValidateCover starts a server-side validation job for the given
+// cover file and polls its status until it finishes or the context
+// expires. See [Client.StartCoverValidation],
+// [Client.GetCoverValidation], and [PollPeriod].
func (c *Client) ValidateCover(ctx context.Context, srcUrl string, mfg PkgId, npages uint) (CoverValidation, error) {
id, err := c.StartCoverValidation(srcUrl, mfg, npages)
if err != nil {
@@ -191,10 +185,11 @@ func (c *Client) ValidateCover(ctx context.Context, srcUrl string, mfg PkgId, np
})
}
-// StartCoverValidation starts a server-side validation job for the cover
-// file located at srcUrl, returning the job ID. mfg is the manufacturing
-// settings of the book, and npages is the number of interior pages. Use
-// GetCoverValidation() to poll the status of the job.
+// StartCoverValidation starts a server-side validation job for the
+// cover file located at srcUrl, returning the job ID. mfg is the
+// manufacturing settings of the book, and npages is the number of
+// interior pages. Use [Client.GetCoverValidation] to poll the status
+// of the job.
//
// https://api.lulu.com/docs/#tag/Files-validation/operation/Validate-Cover_create
func (c *Client) StartCoverValidation(srcUrl string, mfg PkgId, npages uint) (uint, error) {
@@ -384,7 +379,7 @@ func (c *Client) getDecode(path string, v any) error {
// getQueryDecode sends a GET path?query request and unmarshals the response into v.
func (c *Client) getQueryDecode(path string, query url.Values, v any) error {
- url, err := url.JoinPath(ApiUrl, path)
+ url, err := c.url(path)
if err != nil {
return err
}
@@ -419,7 +414,7 @@ func (c *Client) post(path string, payload any) (*http.Response, error) {
if err != nil {
return nil, errEncReq{payload, path, err}
}
- url, err := url.JoinPath(ApiUrl, path)
+ url, err := c.url(path)
if err != nil {
return nil, err
}
@@ -435,7 +430,7 @@ func (c *Client) putDecode(path string, payload any, v any) error {
if err != nil {
return errEncReq{payload, path, err}
}
- url, err := url.JoinPath(ApiUrl, path)
+ url, err := c.url(path)
if err != nil {
return err
}
@@ -458,6 +453,10 @@ func (c *Client) putDecode(path string, payload any, v any) error {
return decodeResponse(resp, v)
}
+func (c *Client) url(path string) (string, error) {
+ return url.JoinPath(c.apiUrl, path)
+}
+
func decodeResponse(resp *http.Response, v any) error {
body, err := io.ReadAll(resp.Body)
if err != nil {