email/watch/
config.rs

1use std::{fmt, future::Future, ops::Deref, pin::Pin, sync::Arc};
2
3use process::Command;
4
5use crate::envelope::Envelope;
6
7/// Watch hook configuration.
8///
9/// Each variant represent the action that should be done when a
10/// change occurs.
11#[derive(Clone, Debug)]
12#[cfg_attr(
13    feature = "derive",
14    derive(serde::Serialize, serde::Deserialize),
15    serde(rename_all = "kebab-case")
16)]
17pub struct WatchHook {
18    /// Execute the shell command.
19    ///
20    /// For now, command is executed without any parameter nor
21    /// input. This may change in the future.
22    pub cmd: Option<Command>,
23
24    /// Send a system notification using the given
25    /// [`notify_rust::Notification`]-like configuration.
26    pub notify: Option<WatchNotifyConfig>,
27
28    /// Execute the given watch function.
29    ///
30    /// The watch function cannot be de/serialized. The function
31    /// should take a reference to an envelope and return a [`Result`]
32    /// of unit.
33    #[cfg_attr(feature = "derive", serde(skip))]
34    pub callback: Option<WatchFn>,
35}
36
37impl Eq for WatchHook {
38    //
39}
40
41impl PartialEq for WatchHook {
42    fn eq(&self, other: &Self) -> bool {
43        self.cmd == other.cmd && self.notify == other.notify
44    }
45}
46
47/// Watch function.
48///
49/// This is just a wrapper around a function that takes a reference to
50/// an envelope.
51#[derive(Clone)]
52pub struct WatchFn(
53    // This function is essentially an asyc Fn with an empty anyhow result type.
54    // So it should not create too much type complexity.
55    // Added to that there is a new function that can take the complexity of
56    // pinning and arcing away.
57    #[allow(clippy::type_complexity)]
58    Arc<
59        dyn Fn(&Envelope) -> Pin<Box<dyn Future<Output = crate::Result<()>> + Send>> + Send + Sync,
60    >,
61);
62
63impl WatchFn {
64    /// Create a new watch function.
65    pub fn new<F: Future<Output = crate::Result<()>> + Send + 'static>(
66        f: impl Fn(&Envelope) -> F + Send + Sync + 'static,
67    ) -> Self {
68        Self(Arc::new(move |envelope| Box::pin(f(envelope))))
69    }
70}
71
72impl Default for WatchFn {
73    fn default() -> Self {
74        Self(Arc::new(|_| Box::pin(async { Ok(()) })))
75    }
76}
77
78impl Deref for WatchFn {
79    type Target = Arc<
80        dyn Fn(&Envelope) -> Pin<Box<dyn Future<Output = crate::Result<()>> + Send>> + Send + Sync,
81    >;
82
83    fn deref(&self) -> &Self::Target {
84        &self.0
85    }
86}
87
88impl fmt::Debug for WatchFn {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        write!(f, "WatchFn()")
91    }
92}
93
94/// The watch configuration of the notify hook variant.
95///
96/// The structure tries to match the [`notify_rust::Notification`] API
97/// and may evolve in the future.
98#[derive(Clone, Debug, Default, Eq, PartialEq)]
99#[cfg_attr(
100    feature = "derive",
101    derive(serde::Serialize, serde::Deserialize),
102    serde(rename_all = "kebab-case")
103)]
104pub struct WatchNotifyConfig {
105    /// The summary (or the title) of the notification.
106    ///
107    /// Accepted placeholders:
108    ///  - "{id}": the id of the envelope
109    ///  - "{subject}": the subject of the envelope
110    ///  - "{sender}" either the sender name or the address
111    ///  - "{sender.name}" the sender name or "unknown"
112    ///  - "{sender.address}" the sender address
113    ///  - "{recipient}" either the recipient name or the address
114    ///  - "{recipient.name}" the recipient name or "unknown"
115    ///  - "{recipient.address}" the recipient address
116    pub summary: String,
117
118    /// The body of the notification.
119    ///
120    /// Accepted placeholders:
121    ///  - "{id}": the id of the envelope
122    ///  - "{subject}": the subject of the envelope
123    ///  - "{sender}" either the sender name or the address
124    ///  - "{sender.name}" the sender name or "unknown"
125    ///  - "{sender.address}" the sender address
126    ///  - "{recipient}" either the recipient name or the address
127    ///  - "{recipient.name}" the recipient name or "unknown"
128    ///  - "{recipient.address}" the recipient address
129    pub body: String,
130}