Import type names instead of writing their paths (#355)

The style guide imports types and uses them unqualified. 222 places
wrote a path such as std::collections::HashMap or std::time::Duration
inline; each file now imports the type once, one use a module, and
names it bare. Done with a script, file by file, only where the name is
unambiguous there:
- one path for the name throughout the file, and not a name the file
  already binds otherwise (its own types, other imports, a function's
  local use);
- not a name generic enough to mislead bare (Error, Result, Value,
  Mutex, Client, Request, Response, Command, Event, Context, Map, Ok);
- only code a normal build compiles, so no import is unused outside
  tests or names a crate only tests depend on.

193 paths stay, those cases above. No behaviour change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 01:19:10 +00:00
parent d33d83043d
commit f22c1993b1
25 changed files with 307 additions and 246 deletions

View File

@@ -14,6 +14,7 @@ use std::sync::Arc;
use std::task::{Context, Poll};
use anyhow::{Context as _, Result};
use http::Extensions;
use hyper_util::client::legacy::connect::{Connection, HttpInfo};
type BoxError = Box<dyn std::error::Error + Send + Sync>;
@@ -153,7 +154,7 @@ where
let allowed = self.allowed.clone();
Box::pin(async move {
let conn = connecting.await.map_err(Into::into)?;
let mut extras = http::Extensions::new();
let mut extras = Extensions::new();
conn.connected().get_extras(&mut extras);
// Refused when the peer is unknown too: a connection this cannot check is one it
// cannot vouch for.

View File

@@ -4,8 +4,13 @@
pub mod guard;
use anyhow::{Context, Result, anyhow};
use chrono::{DateTime, NaiveDateTime};
use reqwest::StatusCode;
use reqwest::header::HeaderName;
use reqwest::header::{ETAG, IF_MODIFIED_SINCE, IF_NONE_MATCH, LAST_MODIFIED};
use std::collections::{HashMap, HashSet};
use std::sync::atomic::AtomicU64;
use std::time::Duration;
/// A feed as `parse` reads it, RSS or Atom alike: the channel's own details and its items.
#[derive(Debug, Default)]
@@ -73,20 +78,20 @@ pub enum Fetched {
/// with no limit one hung server held every scan for as long as it did. Dreamwidth answered 504
/// after 60-67 s for a day, and each scan took 70-80 s instead of 15 (#108). A feed is small;
/// downloads, which are not, have no such limit.
pub const FEED_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
pub const FEED_TIMEOUT: Duration = Duration::from_secs(30);
/// The longest a feed's server may send nothing, for the clients that fetch feeds to set: one
/// that took the connection and never answered ran the whole 30 s, every scan it was in (#230).
/// No feed that answered took over 8 s in a day of traces; one that keeps sending, however
/// slowly, still has until FEED_TIMEOUT.
pub const FEED_SILENCE: std::time::Duration = std::time::Duration::from_secs(15);
pub const FEED_SILENCE: Duration = Duration::from_secs(15);
/// Feeds fetched and the bytes of their bodies, since the process started, for /metrics to set
/// beside memory (#224): a body is read whole, so a night of large feeds is a night of large
/// allocations.
pub static FETCHES: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub static FETCHES: AtomicU64 = AtomicU64::new(0);
/// See `FETCHES`.
pub static FETCHED_BYTES: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub static FETCHED_BYTES: AtomicU64 = AtomicU64::new(0);
/// Conditional GET, following redirects itself: `client` must follow none (`feed_client`), so
/// each hop is seen. The second value is where the feed now is when every hop said so for good
@@ -204,8 +209,7 @@ pub async fn fetch(
// The original surfaced 401/407 specially; the code is enough for a UI to switch on.
return Err(anyhow!("HTTP {status}"));
}
let header =
|h: reqwest::header::HeaderName| resp.headers().get(&h).and_then(|v| v.to_str().ok()).map(str::to_owned);
let header = |h: HeaderName| resp.headers().get(&h).and_then(|v| v.to_str().ok()).map(str::to_owned);
let etag = header(ETAG);
let last_modified = header(LAST_MODIFIED);
let bytes = read_capped(resp).await.context("reading body")?;
@@ -257,7 +261,7 @@ fn from_wp_posts(posts: &[serde_json::Value]) -> ParsedFeed {
link: non_empty(p["link"].as_str()),
published: p["date_gmt"]
.as_str()
.and_then(|d| chrono::NaiveDateTime::parse_from_str(d, "%Y-%m-%dT%H:%M:%S").ok())
.and_then(|d| NaiveDateTime::parse_from_str(d, "%Y-%m-%dT%H:%M:%S").ok())
.map(|d| d.and_utc().timestamp()),
description: non_empty(p["content"]["rendered"].as_str()),
image: non_empty(p["jetpack_featured_media_url"].as_str()),
@@ -647,7 +651,7 @@ fn alternate_feed_link(bytes: &[u8]) -> Option<String> {
/// feed that moved (#102); cnn.com is one.
pub async fn find_feed(client: &reqwest::Client, url: &str) -> Result<String> {
let read = |u: String| async move {
let resp = client.get(&u).timeout(std::time::Duration::from_secs(30)).send().await.context("connecting")?;
let resp = client.get(&u).timeout(Duration::from_secs(30)).send().await.context("connecting")?;
anyhow::ensure!(resp.status().is_success(), "HTTP {}", resp.status());
let base = resp.url().clone();
anyhow::Ok((base, read_capped(resp).await.context("reading")?))
@@ -675,7 +679,7 @@ pub async fn find_feed(client: &reqwest::Client, url: &str) -> Result<String> {
/// `/favicon.ico`. None if neither is there.
#[tracing::instrument(skip_all, fields(site = site))]
pub async fn site_icon(client: &reqwest::Client, site: &str) -> Option<String> {
let timeout = std::time::Duration::from_secs(20);
let timeout = Duration::from_secs(20);
let resp = client.get(site).timeout(timeout).send().await.ok()?;
// Relative to where the page ended up, not where it was asked for: a site that redirects
// to /en/ would otherwise have its icon looked for in the wrong place.
@@ -702,11 +706,7 @@ const MAX_ART_HOSTS: usize = 8;
/// address it does not serve on https stays, for /api/art to fetch (#90). Four of the five
/// hosts the catalogue had on http served the same image on https; The Secret Cabal's CDN
/// presents another name's certificate (#110).
pub async fn prefer_https(
client: &reqwest::Client,
url: &str,
known: &mut std::collections::HashMap<String, bool>,
) -> String {
pub async fn prefer_https(client: &reqwest::Client, url: &str, known: &mut HashMap<String, bool>) -> String {
let Some(rest) = url.strip_prefix("http://") else { return url.to_owned() };
let host = rest.split('/').next().unwrap_or("").to_owned();
let secure = format!("https://{rest}");
@@ -728,7 +728,7 @@ pub async fn prefer_https(
/// Whether `url` answers with an image. A site with no favicon often answers 200 with its home
/// page, which is not an icon.
pub async fn is_image(client: &reqwest::Client, url: &str) -> bool {
let timeout = std::time::Duration::from_secs(20);
let timeout = Duration::from_secs(20);
let Ok(resp) = client.get(url).timeout(timeout).send().await else { return false };
resp.status().is_success()
&& resp
@@ -1037,7 +1037,7 @@ fn non_empty(s: Option<&str>) -> Option<String> {
/// that embeds the file it encloses lists the same file twice: Rands in Repose's "The Promotion
/// Paradox" was downloaded twice and offered two play buttons for one mp3. The first stays.
fn drop_player_repeats(encs: &mut Vec<Enclosure>) {
let mut seen = std::collections::HashSet::new();
let mut seen = HashSet::new();
encs.retain(|e| seen.insert(same_file_key(&e.url)));
}
@@ -1238,10 +1238,7 @@ fn parse_duration(s: &str) -> Option<i64> {
/// RSS pubDate is RFC 2822; some feeds ship RFC 3339 instead.
fn parse_date(s: &str) -> Option<i64> {
let s = s.trim();
chrono::DateTime::parse_from_rfc2822(s)
.or_else(|_| chrono::DateTime::parse_from_rfc3339(s))
.ok()
.map(|d| d.timestamp())
DateTime::parse_from_rfc2822(s).or_else(|_| DateTime::parse_from_rfc3339(s)).ok().map(|d| d.timestamp())
}
/// Apple's podcast categories, each with its subcategories