From 159cf2d4d3be18212ca9b022a5dee235119ab8ec Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 20 Apr 2026 17:01:04 -0600 Subject: [PATCH] refactor(httpcache): sentinel errors for Fetch failure modes ErrUnexpectedStatus, ErrEmptyResponse, ErrSaveMeta are exposed so callers can branch with errors.Is. Messages remain descriptive (status code, URL, Path) via %w wrapping. --- net/httpcache/httpcache.go | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/net/httpcache/httpcache.go b/net/httpcache/httpcache.go index 93ae6a5..952b537 100644 --- a/net/httpcache/httpcache.go +++ b/net/httpcache/httpcache.go @@ -3,6 +3,7 @@ package httpcache import ( "encoding/base64" "encoding/json" + "errors" "fmt" "io" "net" @@ -13,6 +14,21 @@ import ( "time" ) +// Sentinel errors returned by Fetch; wrap with errors.Is to branch on the +// failure mode. The wrapped error always includes the URL or Path context. +var ( + // ErrUnexpectedStatus is returned when the server replies with a + // non-200, non-304 response. + ErrUnexpectedStatus = errors.New("unexpected response status") + + // ErrEmptyResponse is returned when a 200 response body is zero bytes. + ErrEmptyResponse = errors.New("empty response body") + + // ErrSaveMeta is returned when the .meta sidecar cannot be written + // after a successful body download (updated is still true). + ErrSaveMeta = errors.New("save meta sidecar") +) + // BasicAuth returns an HTTP Basic Authorization header value: // "Basic " + base64(user:pass). Pair with the "Authorization" header in // Cacher.Header. @@ -191,7 +207,7 @@ func (c *Cacher) Fetch() (updated bool, err error) { return false, nil } if resp.StatusCode != http.StatusOK { - return false, fmt.Errorf("unexpected status %d fetching %s", resp.StatusCode, c.URL) + return false, fmt.Errorf("%w %d fetching %s", ErrUnexpectedStatus, resp.StatusCode, c.URL) } if err := os.MkdirAll(filepath.Dir(c.Path), 0o755); err != nil { @@ -210,7 +226,7 @@ func (c *Cacher) Fetch() (updated bool, err error) { } if n == 0 { os.Remove(tmp) - return false, fmt.Errorf("empty response from %s", c.URL) + return false, fmt.Errorf("%w from %s", ErrEmptyResponse, c.URL) } if err := os.Rename(tmp, c.Path); err != nil { os.Remove(tmp) @@ -224,7 +240,7 @@ func (c *Cacher) Fetch() (updated bool, err error) { c.lastMod = lm } if err := c.saveMeta(); err != nil { - return true, fmt.Errorf("save meta for %s: %w", c.Path, err) + return true, fmt.Errorf("%w for %s: %w", ErrSaveMeta, c.Path, err) } return true, nil