1use std::{collections::HashMap, path::PathBuf};
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use strum::{Display, EnumString};
6
7#[cfg(feature = "display")]
8use crossterm::style::Stylize;
9
10#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Display, Serialize, EnumString)]
11#[serde(rename_all = "lowercase")]
12#[strum(serialize_all = "lowercase")]
13#[strum(ascii_case_insensitive)]
14#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
15#[typeshare::typeshare]
16pub enum DeploymentState {
17 Pending,
18 Building,
19 Running,
20 #[strum(serialize = "in progress")]
21 InProgress,
22 Stopped,
23 Stopping,
24 Failed,
25
26 #[cfg(feature = "unknown-variants")]
28 #[doc(hidden)]
29 #[typeshare(skip)]
30 #[serde(untagged, skip_serializing)]
31 #[strum(default, to_string = "Unknown: {0}")]
32 Unknown(String),
33}
34
35impl DeploymentState {
36 #[cfg(feature = "display")]
37 pub fn get_color_crossterm(&self) -> crossterm::style::Color {
38 use crossterm::style::Color;
39
40 match self {
41 Self::Pending => Color::DarkYellow,
42 Self::Building => Color::Yellow,
43 Self::InProgress => Color::Cyan,
44 Self::Running => Color::Green,
45 Self::Stopped => Color::DarkBlue,
46 Self::Stopping => Color::Blue,
47 Self::Failed => Color::Red,
48 #[cfg(feature = "unknown-variants")]
49 Self::Unknown(_) => Color::Grey,
50 }
51 }
52 #[cfg(all(feature = "tables", feature = "display"))]
53 pub fn get_color_comfy_table(&self) -> comfy_table::Color {
54 use comfy_table::Color;
55
56 match self {
57 Self::Pending => Color::DarkYellow,
58 Self::Building => Color::Yellow,
59 Self::InProgress => Color::Cyan,
60 Self::Running => Color::Green,
61 Self::Stopped => Color::DarkBlue,
62 Self::Stopping => Color::Blue,
63 Self::Failed => Color::Red,
64 #[cfg(feature = "unknown-variants")]
65 Self::Unknown(_) => Color::Grey,
66 }
67 }
68 #[cfg(feature = "display")]
69 pub fn to_string_colored(&self) -> String {
70 self.to_string()
71 .with(self.get_color_crossterm())
72 .to_string()
73 }
74}
75
76#[derive(Deserialize, Serialize)]
77#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
78#[typeshare::typeshare]
79pub struct DeploymentListResponse {
80 pub deployments: Vec<DeploymentResponse>,
81}
82
83#[derive(Deserialize, Serialize)]
84#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
85#[typeshare::typeshare]
86pub struct DeploymentResponse {
87 pub id: String,
88 pub state: DeploymentState,
89 pub created_at: DateTime<Utc>,
90 pub updated_at: DateTime<Utc>,
91 pub uris: Vec<String>,
93 pub build_id: Option<String>,
94 pub build_meta: Option<BuildMeta>,
95}
96
97#[cfg(feature = "display")]
98impl DeploymentResponse {
99 pub fn to_string_summary_colored(&self) -> String {
100 format!(
102 "Deployment {} - {}",
103 self.id.as_str().bold(),
104 self.state.to_string_colored(),
105 )
106 }
107 pub fn to_string_colored(&self) -> String {
108 format!(
110 "Deployment {} - {}\n{}",
111 self.id.as_str().bold(),
112 self.state.to_string_colored(),
113 self.uris.join("\n"),
114 )
115 }
116}
117
118#[derive(Deserialize, Serialize)]
119#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
120#[typeshare::typeshare]
121pub struct UploadArchiveResponse {
122 pub archive_version_id: String,
124}
125
126#[derive(Deserialize, Serialize)]
127#[serde(tag = "type", content = "content")]
128#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
129#[typeshare::typeshare]
130pub enum DeploymentRequest {
131 BuildArchive(DeploymentRequestBuildArchive),
133 Image(DeploymentRequestImage),
136 }
139
140#[derive(Default, Deserialize, Serialize)]
141#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
142#[typeshare::typeshare]
143pub struct DeploymentRequestBuildArchive {
144 pub archive_version_id: String,
146 pub build_args: Option<BuildArgs>,
147 pub secrets: Option<HashMap<String, String>>,
150 pub build_meta: Option<BuildMeta>,
151}
152
153#[derive(Deserialize, Serialize)]
154#[serde(tag = "type", content = "content")]
155#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
156#[typeshare::typeshare]
157pub enum BuildArgs {
158 Rust(BuildArgsRust),
159 }
162
163#[derive(Deserialize, Serialize)]
164#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
165#[typeshare::typeshare]
166pub struct BuildArgsRust {
167 pub shuttle_runtime_version: Option<String>,
169 pub cargo_chef: bool,
171 pub cargo_build: bool,
173 pub package_name: Option<String>,
175 pub binary_name: Option<String>,
177 pub features: Option<String>,
179 pub no_default_features: bool,
181 pub mold: bool,
183}
184
185impl Default for BuildArgsRust {
186 fn default() -> Self {
187 Self {
188 shuttle_runtime_version: Default::default(),
189 cargo_chef: true,
190 cargo_build: true,
191 package_name: Default::default(),
192 binary_name: Default::default(),
193 features: Default::default(),
194 no_default_features: Default::default(),
195 mold: Default::default(),
196 }
197 }
198}
199
200pub const GIT_STRINGS_MAX_LENGTH: usize = 80;
202
203#[derive(Default, Deserialize, Serialize)]
204#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
205#[typeshare::typeshare]
206pub struct BuildMeta {
207 pub git_commit_id: Option<String>,
208 pub git_commit_msg: Option<String>,
209 pub git_branch: Option<String>,
210 pub git_dirty: Option<bool>,
211}
212
213#[cfg(feature = "display")]
214impl std::fmt::Display for BuildMeta {
215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 if let Some(true) = self.git_dirty {
217 write!(f, "(dirty) ")?;
218 }
219 if let Some(ref c) = self.git_commit_id {
220 write!(f, "[{}] ", c.chars().take(7).collect::<String>())?;
221 }
222 if let Some(ref m) = self.git_commit_msg {
223 write!(f, "{m}")?;
224 }
225
226 Ok(())
227 }
228}
229
230#[derive(Default, Deserialize, Serialize)]
231#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
232#[typeshare::typeshare]
233pub struct DeploymentRequestImage {
234 pub image: String,
235 pub secrets: Option<HashMap<String, String>>,
237 }
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct DeploymentMetadata {
242 pub env: Environment,
243 pub project_name: String,
244 pub storage_path: PathBuf,
246}
247
248#[derive(
250 Clone, Copy, Debug, Default, Display, EnumString, PartialEq, Eq, Serialize, Deserialize,
251)]
252#[serde(rename_all = "lowercase")]
253#[strum(serialize_all = "lowercase")]
254pub enum Environment {
255 #[default]
256 Local,
257 #[strum(serialize = "production")] Deployment,
259 }
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266 use std::str::FromStr;
267
268 #[test]
269 fn deployment_state_from_and_to_str() {
270 assert_eq!(
271 DeploymentState::Building,
272 DeploymentState::from_str("Building").unwrap()
273 );
274 assert_eq!(
275 DeploymentState::Building,
276 DeploymentState::from_str("BuilDing").unwrap()
277 );
278 assert_eq!(
279 DeploymentState::Building,
280 DeploymentState::from_str("building").unwrap()
281 );
282 assert_eq!(
283 DeploymentState::Building.to_string(),
284 "building".to_string()
285 );
286 }
287
288 #[cfg(feature = "unknown-variants")]
289 #[test]
290 fn unknown_state() {
291 assert_eq!(
292 DeploymentState::Unknown("flying".to_string()),
293 DeploymentState::from_str("flying").unwrap()
294 );
295 assert_eq!(
296 DeploymentState::Unknown("flying".to_string()).to_string(),
297 "Unknown: flying".to_string()
298 );
299 }
300
301 #[test]
302 fn env_from_str() {
303 assert_eq!(Environment::Local, Environment::from_str("local").unwrap());
304 assert_eq!(
305 Environment::Deployment,
306 Environment::from_str("production").unwrap()
307 );
308 assert!(Environment::from_str("somewhere_else").is_err());
309 assert_eq!(format!("{:?}", Environment::Local), "Local".to_owned());
310 assert_eq!(format!("{}", Environment::Local), "local".to_owned());
311 assert_eq!(Environment::Local.to_string(), "local".to_owned());
312 }
313}