//! The per-host half of the poller, as a trait. //! //! Everything except three host-specific calls — list unread, mark read, //! resolve own login — is host-agnostic, so a host is described by this //! trait and implemented **inside the binary that polls it**: the Forgejo //! impl lives in `src/bin/hive-forge-notify/`, the GitHub one in //! `src/bin/hive-github-notify/`. Neither binary links the other's //! protocol code, and this library names no host at all. //! //! Implementors hand [`notify`](crate::notify) the same raw JSON either //! way. Forgejo's notifications API is modelled on GitHub's, which is why //! one shared parse works: `id` / `repository.full_name` / `subject` //! `{title,url,latest_comment_url}` / `updated_at` line up field for //! field, and the differences are absorbed by the lenient deserializers //! in `notify` rather than a second parse path. use std::future::Future; /// A polled notification host. /// /// Methods return `impl Future` rather than being `async fn` so the /// returned futures carry an explicit `Send` bound — the poll loops move /// them across tasks. pub trait Source { /// Namespace for this host's todo keys, so two hosts handing out the /// same numeric thread id cannot collide on one todo. /// /// The internal forge deliberately uses an **empty** namespace: its /// keys stay the bare thread ids they have always been, because /// renaming them would orphan every in-flight forge todo on the first /// restart after a deploy. fn key_prefix(&self) -> &'static str; /// Human-readable host name for log lines. fn name(&self) -> &'static str; /// Apply this host's auth — and any headers it requires — to an /// outgoing request. Used by the shared enrichment fetches, which /// follow URLs the server handed us and so must authenticate as /// whichever host produced them. /// /// Hosts differ in *scheme*, not just value, and getting it wrong is /// silent: a request that authenticates as nobody still returns 200, /// just on the unauthenticated rate limit. fn authorize(&self, rb: reqwest::RequestBuilder) -> reqwest::RequestBuilder; /// Resolve the account's own login, for self-notification filtering. /// Returns the empty string on any failure; the caller treats that as /// "filtering disabled" and retries on the next tick. fn own_login(&self, client: &reqwest::Client) -> impl Future + Send; /// Fetch the unread notification page as raw JSON values, plus the /// server's requested minimum seconds between polls when it states /// one (a host that says nothing returns `None` and the caller keeps /// its own cadence). /// /// Raw rather than typed so one unrepresentable field in one item /// cannot poison the whole page; per-item parsing happens in /// [`notify::parse_notification`](crate::notify). `None` means the /// fetch failed — the caller skips this tick. fn list_unread( &self, client: &reqwest::Client, ) -> impl Future, Option)>> + Send; /// Mark a notification thread read on this host. Best-effort: a /// failure leaves the thread unread so it resurfaces next tick, which /// the in-process dedupe map then suppresses from re-waking the agent. fn mark_read(&self, client: &reqwest::Client, id: &str) -> impl Future + Send; }