//! Product data adapters. The deal source is a user-pasted product URL — no //! per-retailer scrapers. Adapters are generic platform readers; the first is //! Shopify, whose storefronts expose a public `/products/{handle}.json` document. use rust_decimal::Decimal; mod shopify; /// Normalised product snapshot produced by an adapter. #[derive(Debug, Clone)] pub struct FetchedProduct { pub title: String, pub price: Decimal, pub currency: String, pub image_url: Option, pub in_stock: Option, pub source: &'static str, } /// A shared HTTP client tuned for storefront fetches. pub fn http_client() -> reqwest::Client { reqwest::Client::builder() .user_agent("consumers-bot/0.1 (+self-hosted wantlist price watcher)") .timeout(std::time::Duration::from_secs(15)) .build() .expect("failed to build reqwest client") } /// Try every adapter in turn. Returns the first that recognises the URL. pub async fn fetch_product( client: &reqwest::Client, url: &str, default_currency: &str, ) -> anyhow::Result { if let Some(p) = shopify::fetch(client, url, default_currency).await? { return Ok(p); } anyhow::bail!("no adapter could read this URL (only Shopify storefronts are supported for now)") }