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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
use std::path::PathBuf;
use std::sync::Arc;

use nameof::name_of;
use tracing_options::TracingOptions;

use super::Host;
use crate::ServiceBuilder;

pub(crate) mod build;
pub mod ports;

pub mod service;
pub use service::*;

pub(crate) mod flamegraph;
pub mod tracing_options;

#[derive(PartialEq, Clone)]
pub enum CrateTarget {
    Default,
    Bin(String),
    Example(String),
}

/// Specifies a crate that uses `hydroflow_deploy_integration` to be
/// deployed as a service.
#[derive(Clone)]
pub struct HydroflowCrate {
    src: PathBuf,
    target: CrateTarget,
    on: Arc<dyn Host>,
    profile: Option<String>,
    rustflags: Option<String>,
    target_dir: Option<PathBuf>,
    no_default_features: bool,
    features: Option<Vec<String>>,
    tracing: Option<TracingOptions>,
    args: Vec<String>,
    display_name: Option<String>,
}

impl HydroflowCrate {
    /// Creates a new `HydroflowCrate` that will be deployed on the given host.
    /// The `src` argument is the path to the crate's directory, and the `on`
    /// argument is the host that the crate will be deployed on.
    pub fn new(src: impl Into<PathBuf>, on: Arc<dyn Host>) -> Self {
        Self {
            src: src.into(),
            target: CrateTarget::Default,
            on,
            profile: None,
            rustflags: None,
            target_dir: None,
            no_default_features: false,
            features: None,
            tracing: None,
            args: vec![],
            display_name: None,
        }
    }

    /// Sets the target to be a binary with the given name,
    /// equivalent to `cargo run --bin <name>`.
    pub fn bin(mut self, bin: impl Into<String>) -> Self {
        if self.target != CrateTarget::Default {
            panic!("{} already set", name_of!(target in Self));
        }

        self.target = CrateTarget::Bin(bin.into());
        self
    }

    /// Sets the target to be an example with the given name,
    /// equivalent to `cargo run --example <name>`.
    pub fn example(mut self, example: impl Into<String>) -> Self {
        if self.target != CrateTarget::Default {
            panic!("{} already set", name_of!(target in Self));
        }

        self.target = CrateTarget::Example(example.into());
        self
    }

    /// Sets the profile to be used when building the crate.
    /// Equivalent to `cargo run --profile <profile>`.
    pub fn profile(mut self, profile: impl Into<String>) -> Self {
        if self.profile.is_some() {
            panic!("{} already set", name_of!(profile in Self));
        }

        self.profile = Some(profile.into());
        self
    }

    pub fn rustflags(mut self, rustflags: impl Into<String>) -> Self {
        if self.rustflags.is_some() {
            panic!("{} already set", name_of!(rustflags in Self));
        }

        self.rustflags = Some(rustflags.into());
        self
    }

    pub fn target_dir(mut self, target_dir: impl Into<PathBuf>) -> Self {
        if self.target_dir.is_some() {
            panic!("{} already set", name_of!(target_dir in Self));
        }

        self.target_dir = Some(target_dir.into());
        self
    }

    pub fn no_default_features(mut self) -> Self {
        self.no_default_features = true;
        self
    }

    pub fn features(mut self, features: impl IntoIterator<Item = impl Into<String>>) -> Self {
        if self.features.is_some() {
            panic!("{} already set", name_of!(features in Self));
        }

        self.features = Some(features.into_iter().map(|s| s.into()).collect());
        self
    }

    pub fn tracing(mut self, perf: impl Into<TracingOptions>) -> Self {
        if self.tracing.is_some() {
            panic!("{} already set", name_of!(tracing in Self));
        }

        self.tracing = Some(perf.into());
        self
    }

    /// Sets the arguments to be passed to the binary when it is launched.
    pub fn args(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
        self.args.extend(args.into_iter().map(|s| s.into()));
        self
    }

    /// Sets the display name for this service, which will be used in logging.
    pub fn display_name(mut self, display_name: impl Into<String>) -> Self {
        if self.display_name.is_some() {
            panic!("{} already set", name_of!(display_name in Self));
        }

        self.display_name = Some(display_name.into());
        self
    }
}

impl ServiceBuilder for HydroflowCrate {
    type Service = HydroflowCrateService;
    fn build(self, id: usize) -> Self::Service {
        let (bin, example) = match self.target {
            CrateTarget::Default => (None, None),
            CrateTarget::Bin(bin) => (Some(bin), None),
            CrateTarget::Example(example) => (None, Some(example)),
        };

        HydroflowCrateService::new(
            id,
            self.src,
            self.on,
            bin,
            example,
            self.profile,
            self.rustflags,
            self.target_dir,
            self.no_default_features,
            self.tracing,
            self.features,
            Some(self.args),
            self.display_name,
            vec![],
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::deployment;

    #[tokio::test]
    async fn test_crate_panic() {
        let mut deployment = deployment::Deployment::new();

        let service = deployment.add_service(
            HydroflowCrate::new("../hydro_cli_examples", deployment.Localhost())
                .example("panic_program")
                .profile("dev"),
        );

        deployment.deploy().await.unwrap();

        let mut stdout = service.try_read().unwrap().stdout();

        deployment.start().await.unwrap();

        assert_eq!(stdout.recv().await.unwrap(), "hello!");

        assert!(stdout.recv().await.is_none());
    }
}