Packages

An Elixir client for Picnic's unofficial supermarket API, built to tolerate drift.

Current section

Files

Jump to
ex_picnic CHANGELOG.md
Raw

CHANGELOG.md

# Changelog
All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Because the Picnic API is unofficial and changes without notice, entries here
also record shape changes that this library absorbs on your behalf.
## [Unreleased]
### Removed
- **Breaking.** `Picnic.order/3`, `Picnic.order!/3`, `Picnic.Resources.Orders`
and `Picnic.Schema.Order` are gone. They called an endpoint that does not
exist in Picnic's API and never returned anything but a 404. Orders are
reached two other ways: a placed order comes back under `"orders"` from
`Picnic.delivery/3`, and the cart is itself the open order, which is why
`Picnic.get_cart/2` returns an order-shaped document. For an order's
checkout status, `Picnic.request/4` reaches
`/cart/checkout/order/{order_id}/status` without needing library support.
### Changed
- **Breaking.** `Picnic.search/3` now returns a flat list of products, most
relevant first, instead of category groups holding their matches under
`"items"`. Picnic moved product search to a display-page endpoint that nests
products at arbitrary depth among banners and headers; the presentation is
dropped and only the products are returned. Fetch the untouched page with
`Picnic.request(client, :get, "/pages/search-page-results", params: [search_term: term])`
if you need the rest.
- **Breaking.** `Picnic.Schema.SearchResult` describes a product rather than a
result group. `:items` and `:links` are gone; `:display_price`, `:price`,
`:image_id`, `:unit_quantity` and `:decorators` take their place. Prices are
integer cents.
### Fixed
- `Picnic.deliveries/2` returned `%Picnic.Error{category: :http, status: 404}`
for every call. The delivery list moved to a separate summary endpoint on the
same API version; the verb, the status-filter body and the response shape are
unchanged, so only the path differs. Note that the list has always returned
delivery *summaries* — fields the full record carries can be `nil` — and
`Picnic.delivery/3` is how you get the complete record. This is now
documented rather than implied.
- Product search returned `%Picnic.Error{category: :http, status: 404}` for
every query. The endpoint it used no longer exists; search now goes to the
page-results endpoint on the same API version. No configuration change is
needed.
- The two-factor handshake could not be completed as documented. `login/3`
returns an error rather than a client on that path, so the client callers
still held carried no token and both `Picnic.Auth.generate_2fa/2` and
`verify_2fa/2` were sent unauthenticated, failing with 401. The error now
carries the provisional client and both functions accept the login result
directly, so no manual `Picnic.Client.put_token/2` step is needed.
- A two-factor challenge returned with a non-success HTTP status discarded the
provisional token from the `x-picnic-auth` header, leaving the handshake
unrecoverable. The challenge is now detected before the status is classified,
so both paths produce the same resumable error.
- The default base URL pointed at a host that does not resolve, so every client
built without an explicit `:base_url` failed with
`%Picnic.Error{category: :network, reason: :nxdomain}` before reaching the
API. Countries now resolve to their real storefronts under
`storefront-prod.<country>.picnicinternational.com`. `:nl` and `:de`
previously resolved to the same host, making `:country` a no-op for URL
building; they are now distinct. Clients that passed `:base_url` explicitly
are unaffected.
### Added
- `Picnic.Page.selling_units/1` flattens Picnic's display-page payloads into a
plain list of products, skipping tiles it does not recognise and degrading to
`[]` with a drift warning on an unfamiliar page.
- `Picnic.login/4` takes options: `send_2fa: true` requests the second-factor
code as part of logging in, with `:channel` (default `"SMS"`) choosing how it
is delivered.
- `Picnic.HTTP.response/4` returns the response for any HTTP status, with
`status_error/1` now public for callers that classify it themselves.
- `Picnic.Client` inspects compactly as `#Picnic.Client<…>` with the auth token
redacted, instead of dumping the whole `Req` request template.
- `:fr` as a supported `:country` value alongside `:nl` and `:de`.
## [0.1.0] - 2026-08-03
Initial release.
### Added
- `Picnic.Client` — the client as a plain struct, not a process. `new/1`
resolves configuration once and stores a `Req.Request` template; there is
nothing to start or supervise.
- `Picnic.request/4` — generic passthrough to any endpoint, including ones this
library does not model. A first-class part of the public API.
- `Picnic.Config` — country, API version, base URL, client id, and default
headers as data. An API version bump is a configuration change, not a code
change.
- `Picnic.Auth` — login, `hash_password/1` (MD5, as Picnic's scheme requires),
token capture from the `x-picnic-auth` header, and an explicit two-factor
flow via `generate_2fa/2` and `verify_2fa/2`.
- `Picnic.Decode` — lenient decoding. Plain maps by default; opt-in
`Picnic.Schema.*` structs that ignore unknown fields, leave missing fields
`nil`, and keep the untouched payload under `:raw`.
- `Picnic.Error` — a categorised error struct
(`:network | :auth | :rate_limit | :http | :decode | :schema`) so callers can
match on classes of failure.
- Resources for search, user, cart, orders, deliveries, and recipes, each with
a bang variant on the `Picnic` facade.
- `Picnic.Session` — optional GenServer that holds the client and
re-authenticates once on an `:auth` error, with a pluggable
`Picnic.Session.TokenStore` and an in-memory default.
- Telemetry: `[:picnic, :request, :start | :stop | :exception]` and
`[:picnic, :schema, :drift]`.
- Retries for transient failures only — network errors, 5xx, and 429 with
backoff. Never for `:auth` or other 4xx.
### Notes
- The `req` dependency is declared as `~> 0.7`, the range the test suite runs
against.
[Unreleased]: https://github.com/emischorr/ex_picnic/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/emischorr/ex_picnic/releases/tag/v0.1.0