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