Index: Cargo.lock ================================================================== --- Cargo.lock +++ Cargo.lock @@ -60,16 +60,10 @@ checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] -[[package]] -name = "anyhow" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" - [[package]] name = "async-attributes" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" @@ -89,13 +83,13 @@ "futures-core", ] [[package]] name = "async-channel" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" dependencies = [ "concurrent-queue", "event-listener-strategy", "futures-core", "pin-project-lite", @@ -133,11 +127,11 @@ name = "async-global-executor" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ - "async-channel 2.3.1", + "async-channel 2.5.0", "async-executor", "async-io 2.4.1", "async-lock 3.4.0", "blocking", "futures-lite 2.6.0", @@ -345,15 +339,15 @@ "generic-array", ] [[package]] name = "blocking" -version = "1.6.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" dependencies = [ - "async-channel 2.3.1", + "async-channel 2.5.0", "async-task", "futures-io", "futures-lite 2.6.0", "piper", ] @@ -397,13 +391,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.27" +version = "1.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" dependencies = [ "shlex", ] [[package]] @@ -442,13 +436,13 @@ "crossbeam-utils", ] [[package]] name = "config" -version = "0.15.11" +version = "0.15.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595aae20e65c3be792d05818e8c63025294ac3cb7e200f11459063a352a6ef80" +checksum = "9baeea16b4f8fc242a701d2abacd87d3b024af0325fb0b59dd16bc14c214c2af" dependencies = [ "pathdiff", "serde", "toml", "winnow", @@ -1179,13 +1173,13 @@ "tower-service", ] [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ "base64", "bytes", "futures-channel", "futures-core", @@ -1369,10 +1363,21 @@ dependencies = [ "hermit-abi 0.3.9", "libc", "windows-sys 0.48.0", ] + +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1666,10 +1671,16 @@ "libc", "pkg-config", "vcpkg", ] +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + [[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" @@ -2003,13 +2014,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.21" +version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8cea6b35bcceb099f30173754403d2eba0a5dc18cea3630fccd88251909288" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "async-compression", "base64", "bytes", "encoding_rs", @@ -2098,13 +2109,12 @@ "quick-xml", ] [[package]] name = "rsstg" -version = "0.4.4" +version = "0.4.5" dependencies = [ - "anyhow", "async-std", "atom_syndication", "chrono", "config", "futures", @@ -2113,10 +2123,11 @@ "regex", "reqwest", "rss", "sedregex", "sqlx", + "stacked_errors", "tgbot", ] [[package]] name = "rustc-demangle" @@ -2283,13 +2294,13 @@ "serde", ] [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ "serde", ] [[package]] @@ -2379,10 +2390,16 @@ name = "slab" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +[[package]] +name = "smallbox" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aca054fd9f8c2ebe8557a2433f307e038c0716124efd045daa0388afa5172189" + [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" @@ -2626,10 +2643,22 @@ [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stacked_errors" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ef11d2fabcf9a75b82a9d80966bde3257410b1245b31f1fb6849103ceda0c3" +dependencies = [ + "owo-colors", + "smallbox", + "thin-vec", + "thiserror", +] [[package]] name = "stringprep" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2728,13 +2757,13 @@ "windows-sys 0.59.0", ] [[package]] name = "tgbot" -version = "0.38.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f93a57a73ceda468ee27ccfa9ee21b35891d72ad0ea9c48ba24ca96621f25ca" +checksum = "210b428621f06584a8a87942861f1bfb248d41b7c38fdcee57c1b6d45d7a0678" dependencies = [ "async-stream", "bytes", "derive_more", "futures-util", @@ -2748,10 +2777,16 @@ "shellwords", "tokio", "tokio-util", ] +[[package]] +name = "thin-vec" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" + [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" @@ -2795,19 +2830,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.1" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "pin-project-lite", + "slab", "socket2 0.5.10", "windows-sys 0.52.0", ] [[package]] @@ -2843,39 +2880,36 @@ "tokio", ] [[package]] name = "toml" -version = "0.8.23" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "f271e09bde39ab52250160a67e88577e0559ad77e9085de6e9051a2c4353f8f8" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" dependencies = [ "serde", ] [[package]] -name = "toml_edit" -version = "0.22.27" +name = "toml_parser" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "b5c1c469eda89749d2230d8156a5969a69ffe0d6d01200581cdc6110674d293e" dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", "winnow", ] [[package]] name = "tower" Index: Cargo.toml ================================================================== --- Cargo.toml +++ Cargo.toml @@ -1,25 +1,25 @@ [package] name = "rsstg" -version = "0.4.4" +version = "0.4.5" authors = ["arcade"] edition = "2021" [dependencies] -anyhow = "1.0.86" async-std = { version = "1.12.0", features = [ "attributes", "tokio1" ] } atom_syndication = { version = "0.12.4", features = [ "with-serde" ] } chrono = "0.4.38" config = { version = "0.15", default-features = false, features = [ "toml" ] } -tgbot = "0.38" +tgbot = "0.39" futures = "0.3.30" futures-util = "0.3.30" lazy_static = "1.5.0" regex = "1.10.6" reqwest = { version = "0.12.7", features = [ "brotli", "socks", "deflate" ]} rss = "2.0.9" sedregex = "0.2.5" +stacked_errors = "0.7.1" sqlx = { version = "0.8", features = [ "postgres", "runtime-async-std-rustls", "chrono", "macros" ], default-features = false } [profile.release] lto = true codegen-units = 1 Index: src/command.rs ================================================================== --- src/command.rs +++ src/command.rs @@ -1,16 +1,15 @@ use crate::core::Core; -use anyhow::{ - anyhow, - bail, - Context, - Result, -}; use lazy_static::lazy_static; use regex::Regex; use sedregex::ReplaceCommand; +use stacked_errors::{ + Result, + StackableErr, + bail, +}; use tgbot::types::{ ChatMember, ChatUsername, GetChat, GetChatAdministrators, @@ -24,49 +23,49 @@ static ref RE_IV_HASH: Regex = Regex::new(r"^[a-f0-9]{14}$").unwrap(); } pub async fn start (core: &Core, msg: &Message) -> Result<()> { core.send("We are open\\. Probably\\. Visit [channel](https://t.me/rsstg_bot_help/3) for details\\.", - Some(msg.chat.get_id()), Some(MarkdownV2)).await?; + Some(msg.chat.get_id()), Some(MarkdownV2)).await.stack()?; Ok(()) } pub async fn list (core: &Core, msg: &Message) -> Result<()> { let sender = msg.sender.get_user_id() - .ok_or(anyhow!("Ignoring unreal users."))?; - let reply = core.list(sender).await?; - core.send(reply, Some(msg.chat.get_id()), Some(MarkdownV2)).await?; + .stack_err("Ignoring unreal users.")?; + let reply = core.list(sender).await.stack()?; + core.send(reply, Some(msg.chat.get_id()), Some(MarkdownV2)).await.stack()?; Ok(()) } pub async fn command (core: &Core, command: &str, msg: &Message, words: &[String]) -> Result<()> { - let mut conn = core.db.begin().await?; + let mut conn = core.db.begin().await.stack()?; let sender = msg.sender.get_user_id() - .ok_or(anyhow!("Ignoring unreal users."))?; + .stack_err("Ignoring unreal users.")?; let reply = if words.len() == 1 { match words[0].parse::() { Err(err) => format!("I need a number.\n{}", &err).into(), Ok(number) => match command { "/check" => core.check(number, false).await .context("Channel check failed.")?.into(), - "/clean" => conn.clean(number, sender).await?, - "/enable" => conn.enable(number, sender).await?.into(), - "/delete" => conn.delete(number, sender).await?, - "/disable" => conn.disable(number, sender).await?.into(), + "/clean" => conn.clean(number, sender).await.stack()?, + "/enable" => conn.enable(number, sender).await.stack()?.into(), + "/delete" => conn.delete(number, sender).await.stack()?, + "/disable" => conn.disable(number, sender).await.stack()?.into(), _ => bail!("Command {command} {words:?} not handled."), }, } } else { "This command needs exacly one number.".into() }; - core.send(reply, Some(msg.chat.get_id()), None).await?; + core.send(reply, Some(msg.chat.get_id()), None).await.stack()?; Ok(()) } pub async fn update (core: &Core, command: &str, msg: &Message, words: &[String]) -> Result<()> { let sender = msg.sender.get_user_id() - .ok_or(anyhow!("Ignoring unreal users."))?; + .stack_err("Ignoring unreal users.")?; let mut source_id: Option = None; let at_least = "Requires at least 3 parameters."; let mut i_words = words.iter(); match command { "/update" => { @@ -124,11 +123,11 @@ } }, None => None, }; let chat_id = ChatUsername::from(channel.as_ref()); - let channel_id = core.tg.execute(GetChat::new(chat_id.clone())).await?.id; + let channel_id = core.tg.execute(GetChat::new(chat_id.clone())).await.stack_err("gettting GetChat")?.id; let chan_adm = core.tg.execute(GetChatAdministrators::new(chat_id)).await .context("Sorry, I have no access to that chat.")?; let (mut me, mut user) = (false, false); for admin in chan_adm { let member_id = match admin { @@ -146,9 +145,9 @@ user = true; } }; if ! me { bail!("I need to be admin on that channel."); }; if ! user { bail!("You should be admin on that channel."); }; - let mut conn = core.db.begin().await?; - core.send(conn.update(source_id, channel, channel_id, url, iv_hash, url_re, sender).await?, Some(msg.chat.get_id()), None).await?; + let mut conn = core.db.begin().await.stack()?; + core.send(conn.update(source_id, channel, channel_id, url, iv_hash, url_re, sender).await.stack()?, Some(msg.chat.get_id()), None).await.stack()?; Ok(()) } Index: src/core.rs ================================================================== --- src/core.rs +++ src/core.rs @@ -9,15 +9,10 @@ BTreeMap, HashSet, }, }; -use anyhow::{ - anyhow, - bail, - Result, -}; use async_std::{ task, sync::{ Arc, Mutex @@ -40,10 +35,16 @@ Update, UpdateType, UserPeerId, }, }; +use stacked_errors::{ + Result, + StackableErr, + anyhow, + bail, +}; lazy_static!{ pub static ref RE_SPECIAL: Regex = Regex::new(r"([\-_*\[\]()~`>#+|{}\.!])").unwrap(); } @@ -63,26 +64,26 @@ http_client: reqwest::Client, } impl Core { pub async fn new(settings: config::Config) -> Result { - let owner_chat = ChatPeerId::from(settings.get_int("owner")?); - let api_key = settings.get_string("api_key")?; - let tg = Client::new(&api_key)?; + let owner_chat = ChatPeerId::from(settings.get_int("owner").stack()?); + let api_key = settings.get_string("api_key").stack()?; + let tg = Client::new(&api_key).stack()?; let mut client = reqwest::Client::builder(); if let Ok(proxy) = settings.get_string("proxy") { - let proxy = reqwest::Proxy::all(proxy)?; + let proxy = reqwest::Proxy::all(proxy).stack()?; client = client.proxy(proxy); } - let http_client = client.build()?; - let me = tg.execute(GetBot).await?; + let http_client = client.build().stack()?; + let me = tg.execute(GetBot).await.stack()?; let core = Core { tg, me, owner_chat, - db: Db::new(&settings.get_string("pg")?)?, + db: Db::new(&settings.get_string("pg").stack()?)?, sources: Arc::new(Mutex::new(HashSet::new())), http_client, // max_delay: 60, }; let clone = core.clone(); @@ -107,19 +108,19 @@ where S: Into { let msg = msg.into(); let mode = mode.unwrap_or(ParseMode::Html); let target = target.unwrap_or(self.owner_chat); - Ok(self.tg.execute( + self.tg.execute( SendMessage::new(target, msg) .with_parse_mode(mode) - ).await?) + ).await.stack() } pub async fn check (&self, id: i32, real: bool) -> Result { let mut posted: i32 = 0; - let mut conn = self.db.begin().await?; + let mut conn = self.db.begin().await.stack()?; let id = { let mut set = self.sources.lock_arc().await; match set.get(&id) { Some(id) => id.clone(), @@ -130,30 +131,30 @@ }, } }; let count = Arc::strong_count(&id); if count == 2 { - let source = conn.get_source(*id, self.owner_chat).await?; - conn.set_scrape(*id).await?; + let source = conn.get_source(*id, self.owner_chat).await.stack()?; + conn.set_scrape(*id).await.stack()?; let destination = ChatPeerId::from(match real { true => source.channel_id, false => source.owner, }); let mut this_fetch: Option> = None; let mut posts: BTreeMap, String> = BTreeMap::new(); - let response = self.http_client.get(&source.url).send().await?; + let response = self.http_client.get(&source.url).send().await.stack()?; let status = response.status(); - let content = response.bytes().await?; + let content = response.bytes().await.stack()?; match rss::Channel::read_from(&content[..]) { Ok(feed) => { for item in feed.items() { if let Some(link) = item.link() { let date = match item.pub_date() { Some(feed_date) => DateTime::parse_from_rfc2822(feed_date), None => DateTime::parse_from_rfc3339(&item.dublin_core_ext().unwrap().dates()[0]), - }?; + }.stack()?; let url = link; posts.insert(date, url.to_string()); } }; }, @@ -176,23 +177,23 @@ _ => bail!("Unsupported or mangled content:\n{:?}\n{err:#?}\n{status:#?}\n", &source.url) } }; for (date, url) in posts.iter() { let post_url: Cow = match source.url_re { - Some(ref x) => sedregex::ReplaceCommand::new(x)?.execute(url), + Some(ref x) => sedregex::ReplaceCommand::new(x).stack()?.execute(url), None => url.into(), }; - if let Some(exists) = conn.exists(&post_url, *id).await? { + if let Some(exists) = conn.exists(&post_url, *id).await.stack()? { if ! exists { if this_fetch.is_none() || *date > this_fetch.unwrap() { this_fetch = Some(*date); }; self.send( match &source.iv_hash { Some(hash) => format!(" {post_url}"), None => format!("{post_url}"), - }, Some(destination), Some(ParseMode::Html)).await?; - conn.add_post(*id, date, &post_url).await?; + }, Some(destination), Some(ParseMode::Html)).await.stack()?; + conn.add_post(*id, date, &post_url).await.stack()?; }; }; posted += 1; }; posts.clear(); @@ -202,12 +203,12 @@ async fn autofetch(&self) -> Result { let mut delay = chrono::Duration::minutes(1); let now = chrono::Local::now(); let queue = { - let mut conn = self.db.begin().await?; - conn.get_queue().await? + let mut conn = self.db.begin().await.stack()?; + conn.get_queue().await.stack()? }; for row in queue { if let Some(next_fetch) = row.next_fetch { if next_fetch < now { if let (Some(owner), Some(source_id)) = (row.owner, row.source_id) { @@ -214,14 +215,14 @@ let clone = Core { owner_chat: ChatPeerId::from(owner), ..self.clone() }; let source = { - let mut conn = self.db.begin().await?; + let mut conn = self.db.begin().await.stack()?; match conn.get_one(owner, source_id).await { Ok(Some(source)) => source.to_string(), - Ok(None) => "Source not found in database?".to_string(), + Ok(None) => "Source not found in database.stack()?".to_string(), Err(err) => format!("Failed to fetch source data:\n{err}"), } }; task::spawn(async move { if let Err(err) = clone.check(source_id, true).await { @@ -235,18 +236,18 @@ } else if next_fetch - now < delay { delay = next_fetch - now; } } }; - Ok(delay.to_std()?) + delay.to_std().stack() } pub async fn list (&self, owner: UserPeerId) -> Result { let mut reply: Vec = vec![]; reply.push("Channels:".into()); - let mut conn = self.db.begin().await?; - for row in conn.get_list(owner).await? { + let mut conn = self.db.begin().await.stack()?; + for row in conn.get_list(owner).await.stack()? { reply.push(row.to_string()); }; Ok(reply.join("\n\n")) } } Index: src/main.rs ================================================================== --- src/main.rs +++ src/main.rs @@ -5,20 +5,24 @@ mod command; mod core; mod sql; -use anyhow::Result; +use stacked_errors::{ + Result, + StackableErr, +}; use tgbot::handler::LongPoll; #[async_std::main] async fn main() -> Result<()> { let settings = config::Config::builder() .add_source(config::File::with_name("rsstg")) - .build()?; + .build() + .stack()?; - let core = core::Core::new(settings).await?; + let core = core::Core::new(settings).await.stack()?; LongPoll::new(core.tg.clone(), core).run().await; Ok(()) } Index: src/sql.rs ================================================================== --- src/sql.rs +++ src/sql.rs @@ -1,14 +1,10 @@ use std::{ borrow::Cow, fmt, }; -use anyhow::{ - Result, - bail, -}; use async_std::sync::{ Arc, Mutex, }; use chrono::{ @@ -20,10 +16,15 @@ Postgres, Row, postgres::PgPoolOptions, pool::PoolConnection, }; +use stacked_errors::{ + Result, + StackableErr, + bail, +}; #[derive(sqlx::FromRow, Debug)] pub struct List { pub source_id: i32, pub channel: String, @@ -32,11 +33,11 @@ pub iv_hash: Option, pub url_re: Option, } impl fmt::Display for List { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { write!(f, "#{} \\*ļøāƒ£ `{}` {}\nšŸ”— `{}`", self.source_id, self.channel, match self.enabled { true => "šŸ”„ enabled", false => "ā›” disabled", }, self.url)?; @@ -76,17 +77,18 @@ Ok(Db ( Arc::new(Mutex::new(PgPoolOptions::new() .max_connections(5) .acquire_timeout(std::time::Duration::new(300, 0)) .idle_timeout(std::time::Duration::new(60, 0)) - .connect_lazy(pguri)?)), + .connect_lazy(pguri) + .stack()?)), )) } pub async fn begin(&self) -> Result { let pool = self.0.lock_arc().await; - let conn = Conn ( pool.acquire().await? ); + let conn = Conn ( pool.acquire().await.stack()? ); Ok(conn) } } pub struct Conn ( @@ -97,20 +99,20 @@ pub async fn add_post (&mut self, source_id: i32, date: &DateTime, post_url: &str) -> Result<()> { sqlx::query("insert into rsstg_post (source_id, posted, url) values ($1, $2, $3);") .bind(source_id) .bind(date) .bind(post_url) - .execute(&mut *self.0).await?; + .execute(&mut *self.0).await.stack()?; Ok(()) } pub async fn clean (&mut self, source_id: i32, owner: I) -> Result> where I: Into { match sqlx::query("delete from rsstg_post p using rsstg_source s where p.source_id = $1 and owner = $2 and p.source_id = s.source_id;") .bind(source_id) .bind(owner.into()) - .execute(&mut *self.0).await?.rows_affected() { + .execute(&mut *self.0).await.stack()?.rows_affected() { 0 => { Ok("No data found found.".into()) }, x => { Ok(format!("{x} posts purged.").into()) }, } } @@ -117,22 +119,22 @@ pub async fn delete (&mut self, source_id: i32, owner: I) -> Result> where I: Into { match sqlx::query("delete from rsstg_source where source_id = $1 and owner = $2;") .bind(source_id) .bind(owner.into()) - .execute(&mut *self.0).await?.rows_affected() { + .execute(&mut *self.0).await.stack()?.rows_affected() { 0 => { Ok("No data found found.".into()) }, - x => { Ok(format!("{} sources removed.", x).into()) }, + x => { Ok(format!("{x} sources removed.").into()) }, } } pub async fn disable (&mut self, source_id: i32, owner: I) -> Result<&str> where I: Into { match sqlx::query("update rsstg_source set enabled = false where source_id = $1 and owner = $2") .bind(source_id) .bind(owner.into()) - .execute(&mut *self.0).await?.rows_affected() { + .execute(&mut *self.0).await.stack()?.rows_affected() { 1 => { Ok("Source disabled.") }, 0 => { Ok("Source not found.") }, _ => { bail!("Database error.") }, } } @@ -140,11 +142,11 @@ pub async fn enable (&mut self, source_id: i32, owner: I) -> Result<&str> where I: Into { match sqlx::query("update rsstg_source set enabled = true where source_id = $1 and owner = $2") .bind(source_id) .bind(owner.into()) - .execute(&mut *self.0).await?.rows_affected() { + .execute(&mut *self.0).await.stack()?.rows_affected() { 1 => { Ok("Source enabled.") }, 0 => { Ok("Source not found.") }, _ => { bail!("Database error.") }, } } @@ -152,52 +154,52 @@ pub async fn exists (&mut self, post_url: &str, id: I) -> Result> where I: Into { let row = sqlx::query("select exists(select true from rsstg_post where url = $1 and source_id = $2) as exists;") .bind(post_url) .bind(id.into()) - .fetch_one(&mut *self.0).await?; - let exists: Option = row.try_get("exists")?; + .fetch_one(&mut *self.0).await.stack()?; + let exists: Option = row.try_get("exists").stack()?; Ok(exists) } pub async fn get_queue (&mut self) -> Result> { let block: Vec = sqlx::query_as("select source_id, next_fetch, owner from rsstg_order natural left join rsstg_source where next_fetch < now() + interval '1 minute';") - .fetch_all(&mut *self.0).await?; + .fetch_all(&mut *self.0).await.stack()?; Ok(block) } pub async fn get_list (&mut self, owner: I) -> Result> where I: Into { let source: Vec = sqlx::query_as("select source_id, channel, enabled, url, iv_hash, url_re from rsstg_source where owner = $1 order by source_id") .bind(owner.into()) - .fetch_all(&mut *self.0).await?; + .fetch_all(&mut *self.0).await.stack()?; Ok(source) } pub async fn get_one (&mut self, owner: I, id: i32) -> Result> where I: Into { let source: Option = sqlx::query_as("select source_id, channel, enabled, url, iv_hash, url_re from rsstg_source where owner = $1 and source_id = $2") .bind(owner.into()) .bind(id) - .fetch_optional(&mut *self.0).await?; + .fetch_optional(&mut *self.0).await.stack()?; Ok(source) } pub async fn get_source (&mut self, id: i32, owner: I) -> Result where I: Into { let source: Source = sqlx::query_as("select channel_id, url, iv_hash, owner, url_re from rsstg_source where source_id = $1 and owner = $2") .bind(id) .bind(owner.into()) - .fetch_one(&mut *self.0).await?; + .fetch_one(&mut *self.0).await.stack()?; Ok(source) } pub async fn set_scrape (&mut self, id: I) -> Result<()> where I: Into { sqlx::query("update rsstg_source set last_scrape = now() where source_id = $1;") .bind(id.into()) - .execute(&mut *self.0).await?; + .execute(&mut *self.0).await.stack()?; Ok(()) } pub async fn update (&mut self, update: Option, channel: &str, channel_id: i64, url: &str, iv_hash: Option<&str>, url_re: Option<&str>, owner: I) -> Result<&str> where I: Into {