Author: Manos Pitsidianakis [manos@pitsidianak.is]
Hash: 0a0aa0467776e429eac45fbecf7918808fb0e2b8
Timestamp: Sat, 16 Sep 2023 10:45:19 +0000 (1 year ago)

+277 -148 +/-16 browse
core: replace error_chain with thiserror
core: replace error_chain with thiserror

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
1diff --git a/Cargo.lock b/Cargo.lock
2index d9c0270..f0373f1 100644
3--- a/Cargo.lock
4+++ b/Cargo.lock
5 @@ -257,7 +257,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
6 dependencies = [
7 "proc-macro2",
8 "quote",
9- "syn 2.0.15",
10+ "syn 2.0.35",
11 ]
12
13 [[package]]
14 @@ -274,7 +274,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
15 dependencies = [
16 "proc-macro2",
17 "quote",
18- "syn 2.0.15",
19+ "syn 2.0.35",
20 ]
21
22 [[package]]
23 @@ -409,7 +409,7 @@ dependencies = [
24 "heck",
25 "proc-macro2",
26 "quote",
27- "syn 2.0.15",
28+ "syn 2.0.35",
29 ]
30
31 [[package]]
32 @@ -650,7 +650,7 @@ dependencies = [
33 "proc-macro2",
34 "quote",
35 "serde_json",
36- "syn 2.0.15",
37+ "syn 2.0.35",
38 "xz2",
39 ]
40
41 @@ -793,7 +793,7 @@ dependencies = [
42 "heck",
43 "proc-macro2",
44 "quote",
45- "syn 2.0.15",
46+ "syn 2.0.35",
47 ]
48
49 [[package]]
50 @@ -976,7 +976,7 @@ dependencies = [
51 "proc-macro2",
52 "quote",
53 "scratch",
54- "syn 2.0.15",
55+ "syn 2.0.35",
56 ]
57
58 [[package]]
59 @@ -993,7 +993,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
60 dependencies = [
61 "proc-macro2",
62 "quote",
63- "syn 2.0.15",
64+ "syn 2.0.35",
65 ]
66
67 [[package]]
68 @@ -1166,15 +1166,6 @@ dependencies = [
69 ]
70
71 [[package]]
72- name = "error-chain"
73- version = "0.12.4"
74- source = "registry+https://github.com/rust-lang/crates.io-index"
75- checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
76- dependencies = [
77- "version_check",
78- ]
79-
80- [[package]]
81 name = "event-listener"
82 version = "2.5.3"
83 source = "registry+https://github.com/rust-lang/crates.io-index"
84 @@ -1388,7 +1379,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
85 dependencies = [
86 "proc-macro2",
87 "quote",
88- "syn 2.0.15",
89+ "syn 2.0.35",
90 ]
91
92 [[package]]
93 @@ -2014,7 +2005,6 @@ version = "0.1.1"
94 dependencies = [
95 "anyhow",
96 "chrono",
97- "error-chain",
98 "jsonschema",
99 "log",
100 "mailpot-tests",
101 @@ -2027,6 +2017,7 @@ dependencies = [
102 "serde_json",
103 "stderrlog",
104 "tempfile",
105+ "thiserror",
106 "toml",
107 "xdg",
108 ]
109 @@ -2476,7 +2467,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
110 dependencies = [
111 "proc-macro2",
112 "quote",
113- "syn 2.0.15",
114+ "syn 2.0.35",
115 ]
116
117 [[package]]
118 @@ -2587,7 +2578,7 @@ dependencies = [
119 "pest_meta",
120 "proc-macro2",
121 "quote",
122- "syn 2.0.15",
123+ "syn 2.0.35",
124 ]
125
126 [[package]]
127 @@ -2730,18 +2721,18 @@ dependencies = [
128
129 [[package]]
130 name = "proc-macro2"
131- version = "1.0.56"
132+ version = "1.0.67"
133 source = "registry+https://github.com/rust-lang/crates.io-index"
134- checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
135+ checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
136 dependencies = [
137 "unicode-ident",
138 ]
139
140 [[package]]
141 name = "quote"
142- version = "1.0.27"
143+ version = "1.0.33"
144 source = "registry+https://github.com/rust-lang/crates.io-index"
145- checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500"
146+ checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
147 dependencies = [
148 "proc-macro2",
149 ]
150 @@ -3078,7 +3069,7 @@ checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6"
151 dependencies = [
152 "proc-macro2",
153 "quote",
154- "syn 2.0.15",
155+ "syn 2.0.35",
156 ]
157
158 [[package]]
159 @@ -3275,9 +3266,9 @@ dependencies = [
160
161 [[package]]
162 name = "syn"
163- version = "2.0.15"
164+ version = "2.0.35"
165 source = "registry+https://github.com/rust-lang/crates.io-index"
166- checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
167+ checksum = "59bf04c28bee9043ed9ea1e41afc0552288d3aba9c6efdd78903b802926f4879"
168 dependencies = [
169 "proc-macro2",
170 "quote",
171 @@ -3336,22 +3327,22 @@ checksum = "9d4ae32d0a4605a89c28534371b056919c12e7a070ee07505af75130ff030111"
172
173 [[package]]
174 name = "thiserror"
175- version = "1.0.40"
176+ version = "1.0.48"
177 source = "registry+https://github.com/rust-lang/crates.io-index"
178- checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
179+ checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
180 dependencies = [
181 "thiserror-impl",
182 ]
183
184 [[package]]
185 name = "thiserror-impl"
186- version = "1.0.40"
187+ version = "1.0.48"
188 source = "registry+https://github.com/rust-lang/crates.io-index"
189- checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
190+ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
191 dependencies = [
192 "proc-macro2",
193 "quote",
194- "syn 2.0.15",
195+ "syn 2.0.35",
196 ]
197
198 [[package]]
199 @@ -3444,7 +3435,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
200 dependencies = [
201 "proc-macro2",
202 "quote",
203- "syn 2.0.15",
204+ "syn 2.0.35",
205 ]
206
207 [[package]]
208 @@ -3570,7 +3561,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
209 dependencies = [
210 "proc-macro2",
211 "quote",
212- "syn 2.0.15",
213+ "syn 2.0.35",
214 ]
215
216 [[package]]
217 diff --git a/core/Cargo.toml b/core/Cargo.toml
218index 4a598b5..9ca1dca 100644
219--- a/core/Cargo.toml
220+++ b/core/Cargo.toml
221 @@ -16,7 +16,6 @@ doc-scrape-examples = true
222 [dependencies]
223 anyhow = "1.0.58"
224 chrono = { version = "^0.4", features = ["serde", ] }
225- error-chain = { version = "0.12.4", default-features = false }
226 jsonschema = { version = "0.17", default-features = false }
227 log = "0.4"
228 melib = { version = "*", default-features = false, features = ["smtp", "unicode_algorithms", "maildir_backend"], git = "https://github.com/meli/meli", rev = "2447a2c" }
229 @@ -25,6 +24,7 @@ percent-encoding = { version = "^2.1" }
230 rusqlite = { version = "^0.28", features = ["bundled", "functions", "trace", "hooks", "serde_json", "array", "chrono", "unlock_notify"] }
231 serde = { version = "^1", features = ["derive", ] }
232 serde_json = "^1"
233+ thiserror = { version = "1.0.48", default-features = false }
234 toml = "^0.5"
235 xdg = "2.4.1"
236
237 diff --git a/core/make_migrations.rs b/core/make_migrations.rs
238index 011970a..4b4acbe 100644
239--- a/core/make_migrations.rs
240+++ b/core/make_migrations.rs
241 @@ -86,15 +86,15 @@ pub fn make_migrations<M: AsRef<Path>, O: AsRef<Path>>(
242 migr_rs
243 .write_all(num.trim_start_matches('0').as_bytes())
244 .unwrap();
245- migr_rs.write_all(b",r###\"").unwrap();
246+ migr_rs.write_all(b",r##\"").unwrap();
247
248 let redo = std::fs::read_to_string(p).unwrap();
249 migr_rs.write_all(redo.trim().as_bytes()).unwrap();
250- migr_rs.write_all(b"\"###,r###\"").unwrap();
251+ migr_rs.write_all(b"\"##,r##\"").unwrap();
252 migr_rs
253 .write_all(std::fs::read_to_string(u).unwrap().trim().as_bytes())
254 .unwrap();
255- migr_rs.write_all(b"\"###),").unwrap();
256+ migr_rs.write_all(b"\"##),").unwrap();
257 if is_data {
258 schema_file.extend(b"\n\n-- ".iter());
259 schema_file.extend(num.as_bytes().iter());
260 diff --git a/core/src/errors.rs b/core/src/errors.rs
261index 8aebb2a..ef37006 100644
262--- a/core/src/errors.rs
263+++ b/core/src/errors.rs
264 @@ -19,58 +19,184 @@
265
266 //! Errors of this library.
267
268- pub use error_chain::ChainedError;
269+ use std::sync::Arc;
270+
271+ use thiserror::Error;
272
273 pub use crate::anyhow::Context;
274
275- // Create the Error, ErrorKind, ResultExt, and Result types
276-
277- error_chain! {
278- errors {
279- /// Post rejected.
280- PostRejected(reason: String) {
281- description("Post rejected")
282- display("Your post has been rejected: {}", reason)
283- }
284-
285- /// An entry was not found in the database.
286- NotFound(model: &'static str) {
287- description("Not found")
288- display("This {} is not present in the database.", model)
289- }
290-
291- /// A request was invalid.
292- InvalidRequest(reason: String) {
293- description("List request is invalid")
294- display("Your list request has been found invalid: {}.", reason)
295- }
296-
297- /// An error happened and it was handled internally.
298- Information(reason: String) {
299- description("An error happened and it was handled internally.")
300- display("{}.", reason)
301- }
302-
303- /// An error that shouldn't happen and should be reported.
304- Bug(reason: String) {
305- description("An error that shouldn't happen and should be reported.")
306- display("{}.", reason)
307- }
308- }
309- foreign_links {
310- External(anyhow::Error) #[doc="Error returned from an external user initiated operation such as deserialization or I/O."];
311- Sql(rusqlite::Error) #[doc="Error returned from sqlite3."];
312- Io(::std::io::Error) #[doc="Error returned from internal I/O operations."];
313- Melib(melib::error::Error) #[doc="Error returned from e-mail protocol operations from `melib` crate."];
314- SerdeJson(serde_json::Error) #[doc="Error from deserializing JSON values."];
315- Template(minijinja::Error) #[doc="Error returned from minijinja template engine."];
316- }
317+ /// Mailpot library error.
318+ #[derive(Error, Debug)]
319+ pub struct Error {
320+ kind: ErrorKind,
321+ source: Option<Arc<Self>>,
322+ }
323+
324+ /// Mailpot library error.
325+ #[derive(Error, Debug)]
326+ pub enum ErrorKind {
327+ /// Post rejected.
328+ #[error("Your post has been rejected: {0}")]
329+ PostRejected(String),
330+ /// An entry was not found in the database.
331+ #[error("This {0} is not present in the database.")]
332+ NotFound(&'static str),
333+ /// A request was invalid.
334+ #[error("Your list request has been found invalid: {0}.")]
335+ InvalidRequest(String),
336+ /// An error happened and it was handled internally.
337+ #[error("An error happened and it was handled internally: {0}.")]
338+ Information(String),
339+ /// An error that shouldn't happen and should be reported.
340+ #[error("An error that shouldn't happen and should be reported: {0}.")]
341+ Bug(String),
342+
343+ /// Error returned from an external user initiated operation such as
344+ /// deserialization or I/O.
345+ #[error(
346+ "Error returned from an external user initiated operation such as deserialization or I/O. \
347+ {0}"
348+ )]
349+ External(#[from] anyhow::Error),
350+ /// Generic
351+ #[error("{0}")]
352+ Generic(anyhow::Error),
353+ /// Error returned from sqlite3.
354+ #[error("Error returned from sqlite3 {0}.")]
355+ Sql(
356+ #[from]
357+ #[source]
358+ rusqlite::Error,
359+ ),
360+ /// Error returned from sqlite3.
361+ #[error("Error returned from sqlite3. {0}")]
362+ SqlLib(
363+ #[from]
364+ #[source]
365+ rusqlite::ffi::Error,
366+ ),
367+ /// Error returned from internal I/O operations.
368+ #[error("Error returned from internal I/O operations. {0}")]
369+ Io(#[from] ::std::io::Error),
370+ /// Error returned from e-mail protocol operations from `melib` crate.
371+ #[error("Error returned from e-mail protocol operations from `melib` crate. {0}")]
372+ Melib(#[from] melib::error::Error),
373+ /// Error from deserializing JSON values.
374+ #[error("Error from deserializing JSON values. {0}")]
375+ SerdeJson(#[from] serde_json::Error),
376+ /// Error returned from minijinja template engine.
377+ #[error("Error returned from minijinja template engine. {0}")]
378+ Template(#[from] minijinja::Error),
379+ }
380+
381+ impl std::fmt::Display for Error {
382+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
383+ write!(fmt, "{}", self.kind)
384+ }
385+ }
386+
387+ impl From<ErrorKind> for Error {
388+ fn from(kind: ErrorKind) -> Self {
389+ Self { kind, source: None }
390+ }
391+ }
392+
393+ macro_rules! impl_from {
394+ ($ty:ty) => {
395+ impl From<$ty> for Error {
396+ fn from(err: $ty) -> Self {
397+ Self {
398+ kind: err.into(),
399+ source: None,
400+ }
401+ }
402+ }
403+ };
404 }
405
406+ impl_from! { anyhow::Error }
407+ impl_from! { rusqlite::Error }
408+ impl_from! { rusqlite::ffi::Error }
409+ impl_from! { ::std::io::Error }
410+ impl_from! { melib::error::Error }
411+ impl_from! { serde_json::Error }
412+ impl_from! { minijinja::Error }
413+
414 impl Error {
415 /// Helper function to create a new generic error message.
416 pub fn new_external<S: Into<String>>(msg: S) -> Self {
417 let msg = msg.into();
418- Self::from(ErrorKind::External(anyhow::Error::msg(msg)))
419+ ErrorKind::External(anyhow::Error::msg(msg)).into()
420+ }
421+
422+ /// Chain an error by introducing a new head of the error chain.
423+ pub fn chain_err<E>(self, lambda: impl FnOnce() -> E) -> Self
424+ where
425+ E: Into<Self>,
426+ {
427+ let new_head: Self = lambda().into();
428+ Self {
429+ source: Some(Arc::new(self)),
430+ ..new_head
431+ }
432+ }
433+
434+ /// Insert a source error into this Error.
435+ pub fn with_source<E>(self, source: E) -> Self
436+ where
437+ E: Into<Self>,
438+ {
439+ Self {
440+ source: Some(Arc::new(source.into())),
441+ ..self
442+ }
443+ }
444+
445+ /// Getter for the kind field.
446+ pub fn kind(&self) -> &ErrorKind {
447+ &self.kind
448+ }
449+
450+ /// Display error chain to user.
451+ pub fn display_chain(&'_ self) -> impl std::fmt::Display + '_ {
452+ ErrorChainDisplay {
453+ current: self,
454+ counter: 1,
455+ }
456+ }
457+ }
458+
459+ impl From<String> for Error {
460+ fn from(s: String) -> Self {
461+ ErrorKind::Generic(anyhow::Error::msg(s)).into()
462+ }
463+ }
464+ impl From<&str> for Error {
465+ fn from(s: &str) -> Self {
466+ ErrorKind::Generic(anyhow::Error::msg(s.to_string())).into()
467+ }
468+ }
469+
470+ /// Type alias for Mailpot library Results.
471+ pub type Result<T> = std::result::Result<T, Error>;
472+
473+ struct ErrorChainDisplay<'e> {
474+ current: &'e Error,
475+ counter: usize,
476+ }
477+
478+ impl std::fmt::Display for ErrorChainDisplay<'_> {
479+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
480+ if let Some(ref source) = self.current.source {
481+ writeln!(fmt, "[{}] {}, caused by:", self.counter, self.current.kind)?;
482+ Self {
483+ current: source,
484+ counter: self.counter + 1,
485+ }
486+ .fmt(fmt)
487+ } else {
488+ writeln!(fmt, "[{}] {}", self.counter, self.current.kind)?;
489+ Ok(())
490+ }
491 }
492 }
493 diff --git a/core/src/lib.rs b/core/src/lib.rs
494index 55f28a4..4f30b93 100644
495--- a/core/src/lib.rs
496+++ b/core/src/lib.rs
497 @@ -151,9 +151,6 @@
498 * - [tag:VERIFY] Verify whether this is the correct way to do something
499 */
500
501- #[macro_use]
502- extern crate error_chain;
503-
504 /// Error library
505 pub extern crate anyhow;
506 /// Date library
507 diff --git a/core/src/message_filters.rs b/core/src/message_filters.rs
508index dd51d2a..7136f8e 100644
509--- a/core/src/message_filters.rs
510+++ b/core/src/message_filters.rs
511 @@ -282,7 +282,10 @@ impl PostFilter for ArchivedAtLink {
512 ctx: &'p mut ListContext<'list>,
513 ) -> std::result::Result<(&'p mut PostEntry, &'p mut ListContext<'list>), ()> {
514 let Some(mut settings) = ctx.filter_settings.remove("ArchivedAtLinkSettings") else {
515- trace!("No ArchivedAtLink settings found for list.pk = {} skipping filter", ctx.list.pk);
516+ trace!(
517+ "No ArchivedAtLink settings found for list.pk = {} skipping filter",
518+ ctx.list.pk
519+ );
520 return Ok((post, ctx));
521 };
522 trace!("Running ArchivedAtLink filter");
523 @@ -392,8 +395,7 @@ impl PostFilter for MimeReject {
524 let enabled = serde_json::from_value::<bool>(map.remove("enabled").unwrap()).unwrap();
525 if !enabled {
526 trace!(
527- "MimeReject is disabled from settings found for list.pk = {} \
528- skipping filter",
529+ "MimeReject is disabled from settings found for list.pk = {} skipping filter",
530 ctx.list.pk
531 );
532 return Ok((post, ctx));
533 diff --git a/core/src/migrations.rs.inc b/core/src/migrations.rs.inc
534index 7f9f7ab..aa1a2d6 100644
535--- a/core/src/migrations.rs.inc
536+++ b/core/src/migrations.rs.inc
537 @@ -1,10 +1,10 @@
538
539 //(user_version, redo sql, undo sql
540- &[(1,r###"PRAGMA foreign_keys=ON;
541- ALTER TABLE templates RENAME TO template;"###,r###"PRAGMA foreign_keys=ON;
542- ALTER TABLE template RENAME TO templates;"###),(2,r###"PRAGMA foreign_keys=ON;
543- ALTER TABLE list ADD COLUMN topics JSON NOT NULL CHECK (json_type(topics) == 'array') DEFAULT '[]';"###,r###"PRAGMA foreign_keys=ON;
544- ALTER TABLE list DROP COLUMN topics;"###),(3,r###"PRAGMA foreign_keys=ON;
545+ &[(1,r##"PRAGMA foreign_keys=ON;
546+ ALTER TABLE templates RENAME TO template;"##,r##"PRAGMA foreign_keys=ON;
547+ ALTER TABLE template RENAME TO templates;"##),(2,r##"PRAGMA foreign_keys=ON;
548+ ALTER TABLE list ADD COLUMN topics JSON NOT NULL CHECK (json_type(topics) == 'array') DEFAULT '[]';"##,r##"PRAGMA foreign_keys=ON;
549+ ALTER TABLE list DROP COLUMN topics;"##),(3,r##"PRAGMA foreign_keys=ON;
550
551 UPDATE list SET topics = arr FROM (SELECT json_group_array(ord.val) AS arr, ord.pk AS pk FROM (SELECT json_each.value AS val, list.pk AS pk FROM list, json_each(list.topics) ORDER BY val ASC) AS ord GROUP BY pk) AS ord WHERE ord.pk = list.pk;
552
553 @@ -23,10 +23,10 @@ AFTER INSERT ON list
554 FOR EACH ROW
555 BEGIN
556 UPDATE list SET topics = arr FROM (SELECT json_group_array(ord.val) AS arr, ord.pk AS pk FROM (SELECT json_each.value AS val, list.pk AS pk FROM list, json_each(list.topics) ORDER BY val ASC) AS ord GROUP BY pk) AS ord WHERE ord.pk = list.pk AND list.pk = NEW.pk;
557- END;"###,r###"PRAGMA foreign_keys=ON;
558+ END;"##,r##"PRAGMA foreign_keys=ON;
559
560 DROP TRIGGER sort_topics_update_trigger;
561- DROP TRIGGER sort_topics_new_trigger;"###),(4,r###"CREATE TABLE IF NOT EXISTS settings_json_schema (
562+ DROP TRIGGER sort_topics_new_trigger;"##),(4,r##"CREATE TABLE IF NOT EXISTS settings_json_schema (
563 pk INTEGER PRIMARY KEY NOT NULL,
564 id TEXT NOT NULL UNIQUE,
565 value JSON NOT NULL CHECK (json_type(value) = 'object'),
566 @@ -192,8 +192,8 @@ WHEN NEW.last_modified == OLD.last_modified
567 BEGIN
568 UPDATE list_settings_json SET last_modified = unixepoch()
569 WHERE pk = NEW.pk;
570- END;"###,r###"DROP TABLE settings_json_schema;
571- DROP TABLE list_settings_json;"###),(5,r###"INSERT OR REPLACE INTO settings_json_schema(id, value) VALUES('ArchivedAtLinkSettings', '{
572+ END;"##,r##"DROP TABLE settings_json_schema;
573+ DROP TABLE list_settings_json;"##),(5,r##"INSERT OR REPLACE INTO settings_json_schema(id, value) VALUES('ArchivedAtLinkSettings', '{
574 "$schema": "http://json-schema.org/draft-07/schema",
575 "$ref": "#/$defs/ArchivedAtLinkSettings",
576 "$defs": {
577 @@ -223,7 +223,7 @@ DROP TABLE list_settings_json;"###),(5,r###"INSERT OR REPLACE INTO settings_json
578 ]
579 }
580 }
581- }');"###,r###"DELETE FROM settings_json_schema WHERE id = 'ArchivedAtLinkSettings';"###),(6,r###"INSERT OR REPLACE INTO settings_json_schema(id, value) VALUES('AddSubjectTagPrefixSettings', '{
582+ }');"##,r##"DELETE FROM settings_json_schema WHERE id = 'ArchivedAtLinkSettings';"##),(6,r##"INSERT OR REPLACE INTO settings_json_schema(id, value) VALUES('AddSubjectTagPrefixSettings', '{
583 "$schema": "http://json-schema.org/draft-07/schema",
584 "$ref": "#/$defs/AddSubjectTagPrefixSettings",
585 "$defs": {
586 @@ -242,7 +242,7 @@ DROP TABLE list_settings_json;"###),(5,r###"INSERT OR REPLACE INTO settings_json
587 ]
588 }
589 }
590- }');"###,r###"DELETE FROM settings_json_schema WHERE id = 'AddSubjectTagPrefixSettings';"###),(7,r###"INSERT OR REPLACE INTO settings_json_schema(id, value) VALUES('MimeRejectSettings', '{
591+ }');"##,r##"DELETE FROM settings_json_schema WHERE id = 'AddSubjectTagPrefixSettings';"##),(7,r##"INSERT OR REPLACE INTO settings_json_schema(id, value) VALUES('MimeRejectSettings', '{
592 "$schema": "http://json-schema.org/draft-07/schema",
593 "$ref": "#/$defs/MimeRejectSettings",
594 "$defs": {
595 @@ -274,4 +274,4 @@ DROP TABLE list_settings_json;"###),(5,r###"INSERT OR REPLACE INTO settings_json
596 "pattern": "^[a-zA-Z!#$&-^_]+[/][a-zA-Z!#$&-^_]+$"
597 }
598 }
599- }');"###,r###"DELETE FROM settings_json_schema WHERE id = 'MimeRejectSettings';"###),]
600\ No newline at end of file
601+ }');"##,r##"DELETE FROM settings_json_schema WHERE id = 'MimeRejectSettings';"##),]
602\ No newline at end of file
603 diff --git a/core/src/models.rs b/core/src/models.rs
604index daa4575..5348eec 100644
605--- a/core/src/models.rs
606+++ b/core/src/models.rs
607 @@ -42,9 +42,9 @@ use melib::email::Address;
608 /// ```
609 #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
610 #[serde(transparent)]
611- pub struct DbVal<T>(pub T, #[serde(skip)] pub i64);
612+ pub struct DbVal<T: Send + Sync>(pub T, #[serde(skip)] pub i64);
613
614- impl<T> DbVal<T> {
615+ impl<T: Send + Sync> DbVal<T> {
616 /// Primary key.
617 #[inline(always)]
618 pub fn pk(&self) -> i64 {
619 @@ -60,21 +60,27 @@ impl<T> DbVal<T> {
620
621 impl<T> std::borrow::Borrow<T> for DbVal<T>
622 where
623- T: Sized,
624+ T: Send + Sync + Sized,
625 {
626 fn borrow(&self) -> &T {
627 &self.0
628 }
629 }
630
631- impl<T> std::ops::Deref for DbVal<T> {
632+ impl<T> std::ops::Deref for DbVal<T>
633+ where
634+ T: Send + Sync,
635+ {
636 type Target = T;
637 fn deref(&self) -> &T {
638 &self.0
639 }
640 }
641
642- impl<T> std::ops::DerefMut for DbVal<T> {
643+ impl<T> std::ops::DerefMut for DbVal<T>
644+ where
645+ T: Send + Sync,
646+ {
647 fn deref_mut(&mut self) -> &mut Self::Target {
648 &mut self.0
649 }
650 @@ -82,7 +88,7 @@ impl<T> std::ops::DerefMut for DbVal<T> {
651
652 impl<T> std::fmt::Display for DbVal<T>
653 where
654- T: std::fmt::Display,
655+ T: std::fmt::Display + Send + Sync,
656 {
657 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
658 write!(fmt, "{}", self.0)
659 diff --git a/core/src/policies.rs b/core/src/policies.rs
660index 259c796..432495b 100644
661--- a/core/src/policies.rs
662+++ b/core/src/policies.rs
663 @@ -154,10 +154,9 @@ mod post_policy {
664 || policy.open
665 || policy.custom)
666 {
667- return Err(
668- "Cannot add empty policy. Having no policies is probably what you want to do."
669- .into(),
670- );
671+ return Err(Error::new_external(
672+ "Cannot add empty policy. Having no policies is probably what you want to do.",
673+ ));
674 }
675 let list_pk = policy.list;
676
677 @@ -347,10 +346,9 @@ mod subscription_policy {
678 policy: SubscriptionPolicy,
679 ) -> Result<DbVal<SubscriptionPolicy>> {
680 if !(policy.open || policy.manual || policy.request || policy.custom) {
681- return Err(
682- "Cannot add empty policy. Having no policy is probably what you want to do."
683- .into(),
684- );
685+ return Err(Error::new_external(
686+ "Cannot add empty policy. Having no policy is probably what you want to do.",
687+ ));
688 }
689 let list_pk = policy.list;
690
691 diff --git a/core/src/postfix.rs b/core/src/postfix.rs
692index 4673d74..0cb7b05 100644
693--- a/core/src/postfix.rs
694+++ b/core/src/postfix.rs
695 @@ -142,7 +142,9 @@ flags=RX user={username}{group_sep}{groupname} directory={{{data_dir}}} argv={{{
696 }
697 };
698
699- let Some(width): Option<usize> = lists.iter().map(|(l, p)| calc_width(l, p.as_deref())).max() else {
700+ let Some(width): Option<usize> =
701+ lists.iter().map(|(l, p)| calc_width(l, p.as_deref())).max()
702+ else {
703 return ret;
704 };
705
706 @@ -281,7 +283,9 @@ flags=RX user={username}{group_sep}{groupname} directory={{{data_dir}}} argv={{{
707 pub fn save_maps(&self, config: &Configuration) -> Result<()> {
708 let db = Connection::open_db(config.clone())?;
709 let Some(postmap) = find_binary_in_path("postmap") else {
710- return Err(Error::from(ErrorKind::External(anyhow::Error::msg("Could not find postmap binary in PATH."))));
711+ return Err(Error::from(ErrorKind::External(anyhow::Error::msg(
712+ "Could not find postmap binary in PATH.",
713+ ))));
714 };
715 let lists = db.lists()?;
716 let lists_post_policies = lists
717 diff --git a/core/src/posts.rs b/core/src/posts.rs
718index fa00cfc..777d09f 100644
719--- a/core/src/posts.rs
720+++ b/core/src/posts.rs
721 @@ -195,9 +195,7 @@ impl Connection {
722 };
723 let result = filters
724 .into_iter()
725- .fold(Ok((&mut post, &mut list_ctx)), |p, f| {
726- p.and_then(|(p, c)| f.feed(p, c))
727- });
728+ .try_fold((&mut post, &mut list_ctx), |(p, c), f| f.feed(p, c));
729 trace!("result {:#?}", result);
730
731 let PostEntry { bytes, action, .. } = post;
732 diff --git a/core/tests/authorizer.rs b/core/tests/authorizer.rs
733index b5fa1ca..f4e124a 100644
734--- a/core/tests/authorizer.rs
735+++ b/core/tests/authorizer.rs
736 @@ -17,9 +17,7 @@
737 * along with this program. If not, see <https://www.gnu.org/licenses/>.
738 */
739
740- use std::error::Error;
741-
742- use mailpot::{models::*, Configuration, Connection, SendMail};
743+ use mailpot::{models::*, Configuration, Connection, ErrorKind, SendMail};
744 use mailpot_tests::init_stderr_logging;
745 use tempfile::TempDir;
746
747 @@ -64,14 +62,15 @@ fn test_authorizer() {
748 .unwrap_err(),
749 ] {
750 assert_eq!(
751- err.source()
752- .unwrap()
753- .downcast_ref::<rusqlite::ffi::Error>()
754- .unwrap(),
755- &rusqlite::ffi::Error {
756- code: rusqlite::ErrorCode::AuthorizationForStatementDenied,
757- extended_code: 23
758- },
759+ err.kind().to_string(),
760+ ErrorKind::Sql(rusqlite::Error::SqliteFailure(
761+ rusqlite::ffi::Error {
762+ code: rusqlite::ErrorCode::AuthorizationForStatementDenied,
763+ extended_code: 23,
764+ },
765+ Some("not authorized".into()),
766+ ))
767+ .to_string()
768 );
769 }
770 assert!(db.lists().unwrap().is_empty());
771 diff --git a/core/tests/migrations.rs b/core/tests/migrations.rs
772index 25fa17e..2cf7859 100644
773--- a/core/tests/migrations.rs
774+++ b/core/tests/migrations.rs
775 @@ -185,8 +185,8 @@ fn test_migration_gen() {
776
777 make_migrations(&in_path, &out_path, &mut vec![]);
778 let output = std::fs::read_to_string(&out_path).unwrap();
779- assert_eq!(&output.replace([' ', '\n'], ""), &r####"//(user_version, redo sql, undo sql
780- &[(1,r###"ALTER TABLE PERSON ADD COLUMN interests TEXT;"###,r###"ALTER TABLE PERSON DROP COLUMN interests;"###),(2,r###"CREATE TABLE hobby ( pk INTEGER PRIMARY KEY NOT NULL,title TEXT NOT NULL);"###,r###"DROP TABLE hobby;"###),(3,r###"ALTER TABLE PERSON ADD COLUMN main_hobby INTEGER REFERENCES hobby(pk) ON DELETE SET NULL;"###,r###"ALTER TABLE PERSON DROP COLUMN main_hobby;"###),]"####.replace([' ', '\n'], ""));
781+ assert_eq!(&output.replace([' ', '\n'], ""), &r###"//(user_version, redo sql, undo sql
782+ &[(1,r##"ALTER TABLE PERSON ADD COLUMN interests TEXT;"##,r##"ALTER TABLE PERSON DROP COLUMN interests;"##),(2,r##"CREATE TABLE hobby ( pk INTEGER PRIMARY KEY NOT NULL,title TEXT NOT NULL);"##,r##"DROP TABLE hobby;"##),(3,r##"ALTER TABLE PERSON ADD COLUMN main_hobby INTEGER REFERENCES hobby(pk) ON DELETE SET NULL;"##,r##"ALTER TABLE PERSON DROP COLUMN main_hobby;"##),]"###.replace([' ', '\n'], ""));
783 }
784
785 #[test]
786 diff --git a/rest-http/src/routes/list.rs b/rest-http/src/routes/list.rs
787index e7f4211..7fdfaad 100644
788--- a/rest-http/src/routes/list.rs
789+++ b/rest-http/src/routes/list.rs
790 @@ -69,16 +69,14 @@ async fn all_lists(
791 let lists_values = db.lists()?;
792 let page = page.unwrap_or(0);
793 let Some(count) = count else {
794- let mut stmt = db
795- .connection
796- .prepare("SELECT count(*) FROM list;")?;
797+ let mut stmt = db.connection.prepare("SELECT count(*) FROM list;")?;
798 return Ok(Json(GetResponse {
799 entries: vec![],
800 total: stmt.query_row([], |row| {
801- let count: usize = row.get(0)?;
802- Ok(count)
803- })?,
804- start: 0,
805+ let count: usize = row.get(0)?;
806+ Ok(count)
807+ })?,
808+ start: 0,
809 }));
810 };
811 let offset = page * count;
812 diff --git a/web/src/typed_paths.rs b/web/src/typed_paths.rs
813index 9429756..c21656d 100644
814--- a/web/src/typed_paths.rs
815+++ b/web/src/typed_paths.rs
816 @@ -171,29 +171,37 @@ list_id_impl!(list_candidates_path, ListEditCandidatesPath);
817
818 macro_rules! list_post_impl {
819 ($ident:ident, $ty:tt) => {
820- pub fn $ident(state: &minijinja::State, id: Value, msg_id: Value) -> std::result::Result<Value, Error> {
821+ pub fn $ident(
822+ state: &minijinja::State,
823+ id: Value,
824+ msg_id: Value,
825+ ) -> std::result::Result<Value, Error> {
826 urlize(state, {
827- let Some(msg_id) = msg_id.as_str().map(|s| if s.starts_with('<') && s.ends_with('>') { s.to_string() } else {
828- format!("<{s}>")
829+ let Some(msg_id) = msg_id.as_str().map(|s| {
830+ if s.starts_with('<') && s.ends_with('>') {
831+ s.to_string()
832+ } else {
833+ format!("<{s}>")
834+ }
835 }) else {
836 return Err(Error::new(
837- minijinja::ErrorKind::UnknownMethod,
838- "Second argument of list_post_path must be a string."
839+ minijinja::ErrorKind::UnknownMethod,
840+ "Second argument of list_post_path must be a string.",
841 ));
842 };
843
844 if let Some(id) = id.as_str() {
845 Value::from(
846 $ty(ListPathIdentifier::Id(id.to_string()), msg_id)
847- .to_crumb()
848- .to_string(),
849+ .to_crumb()
850+ .to_string(),
851 )
852 } else {
853 let pk = id.try_into()?;
854 Value::from(
855 $ty(ListPathIdentifier::Pk(pk), msg_id)
856- .to_crumb()
857- .to_string(),
858+ .to_crumb()
859+ .to_string(),
860 )
861 }
862 })
863 diff --git a/web/src/utils.rs b/web/src/utils.rs
864index 4bfa0a4..03f25ac 100644
865--- a/web/src/utils.rs
866+++ b/web/src/utils.rs
867 @@ -485,6 +485,8 @@ pub fn thread_roots(
868 tref.date,
869 ));
870 }
871+ // clippy: error: temporary with significant `Drop` can be early dropped
872+ drop(env_lock);
873 ret.sort_by_key(|(_, _, key)| std::cmp::Reverse(*key));
874 ret
875 }