1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use futures::Future;
use process::Command;
use std::{fmt, ops::Deref, pin::Pin, sync::Arc};

use crate::envelope::Envelope;

/// Watch hook configuration.
///
/// Each variant represent the action that should be done when a
/// change occurs.
#[derive(Clone, Debug)]
#[cfg_attr(
    feature = "derive",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "kebab-case")
)]
pub struct WatchHook {
    /// Execute the shell command.
    ///
    /// For now, command is executed without any parameter nor
    /// input. This may change in the future.
    pub cmd: Option<Command>,

    /// Send a system notification using the given
    /// [`notify_rust::Notification`]-like configuration.
    pub notify: Option<WatchNotifyConfig>,

    /// Execute the given watch function.
    ///
    /// The watch function cannot be de/serialized. The function
    /// should take a reference to an envelope and return a [`Result`]
    /// of unit.
    #[cfg_attr(feature = "derive", serde(skip))]
    pub callback: Option<WatchFn>,
}

impl Eq for WatchHook {
    //
}

impl PartialEq for WatchHook {
    fn eq(&self, other: &Self) -> bool {
        self.cmd == other.cmd && self.notify == other.notify
    }
}

/// Watch function.
///
/// This is just a wrapper around a function that takes a reference to
/// an envelope.
#[derive(Clone)]
pub struct WatchFn(
    // This function is essentially an asyc Fn with an empty anyhow result type.
    // So it should not create too much type complexity.
    // Added to that there is a new function that can take the complexity of
    // pinning and arcing away.
    #[allow(clippy::type_complexity)]
    Arc<
        dyn Fn(&Envelope) -> Pin<Box<dyn Future<Output = crate::Result<()>> + Send>> + Send + Sync,
    >,
);

impl WatchFn {
    /// Create a new watch function.
    pub fn new<F: Future<Output = crate::Result<()>> + Send + 'static>(
        f: impl Fn(&Envelope) -> F + Send + Sync + 'static,
    ) -> Self {
        Self(Arc::new(move |envelope| Box::pin(f(envelope))))
    }
}

impl Default for WatchFn {
    fn default() -> Self {
        Self(Arc::new(|_| Box::pin(async { Ok(()) })))
    }
}

impl Deref for WatchFn {
    type Target = Arc<
        dyn Fn(&Envelope) -> Pin<Box<dyn Future<Output = crate::Result<()>> + Send>> + Send + Sync,
    >;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl fmt::Debug for WatchFn {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "WatchFn()")
    }
}

/// The watch configuration of the notify hook variant.
///
/// The structure tries to match the [`notify_rust::Notification`] API
/// and may evolve in the future.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(
    feature = "derive",
    derive(serde::Serialize, serde::Deserialize),
    serde(rename_all = "kebab-case")
)]
pub struct WatchNotifyConfig {
    /// The summary (or the title) of the notification.
    ///
    /// Accepted placeholders:
    ///  - "{id}": the id of the envelope
    ///  - "{subject}": the subject of the envelope
    ///  - "{sender}" either the sender name or the address
    ///  - "{sender.name}" the sender name or "unknown"
    ///  - "{sender.address}" the sender address
    ///  - "{recipient}" either the recipient name or the address
    ///  - "{recipient.name}" the recipient name or "unknown"
    ///  - "{recipient.address}" the recipient address
    pub summary: String,

    /// The body of the notification.
    ///
    /// Accepted placeholders:
    ///  - "{id}": the id of the envelope
    ///  - "{subject}": the subject of the envelope
    ///  - "{sender}" either the sender name or the address
    ///  - "{sender.name}" the sender name or "unknown"
    ///  - "{sender.address}" the sender address
    ///  - "{recipient}" either the recipient name or the address
    ///  - "{recipient.name}" the recipient name or "unknown"
    ///  - "{recipient.address}" the recipient address
    pub body: String,
}