Skip to main content

proxy_watch/
lib.rs

1//! Detect — and on Windows, macOS and Linux *watch* — the operating system's proxy
2//! settings.
3//!
4//! [`read`] answers once, starting no watcher and arming no notification route:
5//!
6//! ```no_run
7//! use proxy_watch::{Scheme, read};
8//!
9//! let config = read()?;
10//! match config.effective.endpoint_for(Scheme::Https) {
11//!     Some(endpoint) => println!("https goes through {endpoint}"),
12//!     None => println!("https is direct"),
13//! }
14//! # Ok::<(), proxy_watch::Error>(())
15//! ```
16//!
17//! [`ProxyWatcher`] follows the settings instead — a [`Stream`] of [`WatchEvent`] that
18//! opens with a snapshot, then publishes changes coalesced over
19//! [`WatchOptions::debounce`] (200 ms). It runs its own OS threads, so it needs no async
20//! runtime. Where there is no store to read — any other platform, or a Linux host with no
21//! desktop — the answer is [`Error::Unsupported`], which means "read `ProxyEnv::from_env()`
22//! instead", not "give up".
23//!
24//! `resolve()` (behind the `resolve` feature, so unlinked here) turns a snapshot and a URL
25//! into the ordered endpoints to try, bypass rules applied; a host on PAC or WPAD stops it
26//! with [`Error::PacNotSupported`] until `pac` and an engine — `pac-boa`, or
27//! `pac-windows-native` on Windows — are on. The remaining flags are `linux-gnome` and
28//! `linux-kde` (the Linux stores, on by default), `tokio` for a
29//! `tokio::sync::watch::Receiver`, and `tracing` for lifecycle logs.
30//!
31//! The README carries the feature table, the per-OS map of where these settings live, and a
32//! runnable example for each of the above.
33//!
34//! # Platform coverage
35//!
36//! Windows, GNOME and KDE are exercised on real machines. Under Flatpak or snap without
37//! dconf the Linux backend falls back to the desktop portal, which has no change
38//! notification of its own — set [`WatchOptions::poll_interval`] to poll — or reports
39//! [`Error::Sandboxed`].
40//!
41//! **Unverified:** the macOS backend and that sandbox path have only ever run in CI — no
42//! Mac, no Flatpak, no Snap.
43//! **Risk:** macOS reads a missing or unexpected answer as "nothing is configured" rather
44//! than as an error, so a host that does have a proxy is reported as having none. The
45//! sandbox path fails loudly instead, so what is open there is whether the detection fires
46//! at all, not what it does once it has.
47//! **Symptom:** on a machine whose settings plainly show a proxy, the stream yields
48//! [`ProxyMode::Direct`] and never publishes an error. `src/sys/mac/mod.rs`,
49//! `src/sys/linux/sandbox.rs` and `src/sys/linux/portal.rs` carry the per-backend form.
50// Doc examples compile, but rustdoc discards the warnings of the ones that pass — so a
51// dead `use` in a snippet a reader is shown survives every command CI runs,
52// `RUSTDOCFLAGS=-D warnings` included. Denying it turns that silence into a failing test.
53// Narrow on purpose: an unused *binding* can be a legitimate way to show a type in an
54// example, and its only escape hatch is an underscore the reader can see. An unused
55// import has no legitimate form.
56#![doc(test(attr(deny(unused_imports))))]
57// Paired with `rustdoc-args = ["--cfg", "docsrs"]` in `Cargo.toml`: neither half does
58// anything alone. Together they put an "Available on crate feature `resolve`" banner on
59// every gated item, which the docs.rs page — built with all features on — otherwise omits,
60// leaving a reader no way to see which items a default dependency line compiles.
61#![cfg_attr(docsrs, feature(doc_cfg))]
62
63mod auth;
64#[cfg(feature = "tokio")]
65mod bridge;
66mod bypass;
67mod config;
68#[cfg(test)]
69mod debug_masking; // hand-written Debug gate
70mod diagnostic;
71mod endpoint;
72mod env;
73mod error;
74mod mode;
75#[cfg(feature = "resolve")]
76mod resolve;
77mod sys;
78mod trace;
79mod util;
80mod watch;
81
82#[cfg(feature = "pac")]
83pub mod pac;
84pub mod parse;
85
86pub use crate::auth::ProxyAuth;
87pub use crate::bypass::{BypassRules, HostPattern, LOCAL_TOKEN, NO_LOOPBACK_TOKEN};
88pub use crate::config::{EnvPrecedence, ProxyConfig, ProxyConfigSource};
89pub use crate::diagnostic::{RejectedValue, RejectionKind, RejectionSource};
90pub use crate::endpoint::{ProxyEndpoint, ProxyEntry, ProxyScheme, Scheme};
91pub use crate::env::{CGI_MARKER_VAR, ProxyEnv};
92pub use crate::error::Error;
93pub use crate::mode::ProxyMode;
94pub use crate::watch::{
95    DEFAULT_DEBOUNCE, ProxyWatcher, WatchEvent, WatchHealth, WatchOptions, WatchState, read,
96    read_with_options,
97};
98
99/// Routing decisions, behind the `resolve` feature (on by default).
100#[cfg(feature = "resolve")]
101pub use crate::resolve::{ProxyStep, resolve};
102
103/// Routing decisions that may need a PAC script, behind the `pac` feature (off by
104/// default).
105#[cfg(feature = "pac")]
106pub use crate::resolve::resolve_with_pac;
107
108/// The tokio bridge, behind the `tokio` feature (off by default).
109#[cfg(feature = "tokio")]
110pub use crate::bridge::watch_channel;
111
112/// Re-export of [`futures_core::Stream`].
113pub use futures_core::Stream;
114
115/// Re-export of [`url::Host`] (IPv6 brackets already resolved).
116pub use url::Host;
117
118/// Re-export of [`ipnet::IpNet`] for [`HostPattern::Cidr`].
119pub use ipnet::IpNet;
120
121/// Re-export of [`url::Url`] for [`ProxyMode::Pac`].
122pub use url::Url;
123
124// Hands the README to rustdoc so that `cargo test` compiles its examples. They are the
125// first code a reader runs, and the one place a signature change is invisible to `cargo
126// build`. Keep each block whole: rustdoc's `# ` hidden lines render literally on GitHub,
127// so a block only compiles here if it also reads as a complete program there.
128//
129// `resolve` gates the item because one example calls `resolve()`, which the feature-off
130// rows of the matrix do not compile.
131#[cfg(all(doctest, feature = "resolve"))]
132#[doc = include_str!("../README.md")]
133struct ReadmeDoctests;