Refactor the internal HTTP helper from get() returning raw bytes to
getAndDecode() that streams directly into the target struct via
json.NewDecoder. This eliminates the intermediate []byte allocation
from io.ReadAll on every API response.
The new decoder also validates that responses contain exactly one JSON
value by attempting a second Decode after the primary one — any content
beyond the first value (e.g., concatenated objects from a misbehaving
proxy) returns an error instead of silently discarding it.
Changes:
- api/client.go: Replace get() with getAndDecode(), update FetchStores
and FetchSavings callers to use the new signature
- api/client_test.go: Add TestFetchSavings_TrailingJSONIsRejected and
TestFetchStores_MalformedJSONReturnsDecodeError covering the new
decoder error paths
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
HTTP client that wraps the Publix services API with two endpoints:
- /api/v4/savings — fetches weekly ad deals for a given store number
- /api/v1/storelocation — finds nearby stores by ZIP code
Includes request types (SavingsResponse, SavingItem, StoreResponse,
Store) mapping directly to the Publix JSON schema. The client sends
a PublixStore header for store-scoped requests and uses a 15-second
timeout. Tests use httptest servers to verify header propagation,
JSON decoding, and error handling for non-200 responses.