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#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
12#[serde(rename_all = "lowercase")]
13#[strum(serialize_all = "lowercase")]
14#[strum(ascii_case_insensitive)]
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 Unknown,
27}
28
29impl DeploymentState {
30 #[cfg(feature = "display")]
31 pub fn get_color_crossterm(&self) -> crossterm::style::Color {
32 use crossterm::style::Color;
33
34 match self {
35 Self::Pending => Color::DarkYellow,
36 Self::Building => Color::Yellow,
37 Self::InProgress => Color::Cyan,
38 Self::Running => Color::Green,
39 Self::Stopped => Color::DarkBlue,
40 Self::Stopping => Color::Blue,
41 Self::Failed => Color::Red,
42 Self::Unknown => Color::Grey,
43 }
44 }
45 #[cfg(all(feature = "tables", feature = "display"))]
46 pub fn get_color_comfy_table(&self) -> comfy_table::Color {
47 use comfy_table::Color;
48
49 match self {
50 Self::Pending => Color::DarkYellow,
51 Self::Building => Color::Yellow,
52 Self::InProgress => Color::Cyan,
53 Self::Running => Color::Green,
54 Self::Stopped => Color::DarkBlue,
55 Self::Stopping => Color::Blue,
56 Self::Failed => Color::Red,
57 Self::Unknown => Color::Grey,
58 }
59 }
60 #[cfg(feature = "display")]
61 pub fn to_string_colored(&self) -> String {
62 self.to_string()
63 .with(self.get_color_crossterm())
64 .to_string()
65 }
66}
67
68#[derive(Deserialize, Serialize)]
69#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
70#[typeshare::typeshare]
71pub struct DeploymentListResponse {
72 pub deployments: Vec<DeploymentResponse>,
73}
74
75#[derive(Deserialize, Serialize)]
76#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
77#[typeshare::typeshare]
78pub struct DeploymentResponse {
79 pub id: String,
80 pub state: DeploymentState,
81 pub created_at: DateTime<Utc>,
82 pub updated_at: DateTime<Utc>,
83 pub uris: Vec<String>,
85 pub build_id: Option<String>,
86 pub build_meta: Option<BuildMeta>,
87}
88
89#[cfg(feature = "display")]
90impl DeploymentResponse {
91 pub fn to_string_summary_colored(&self) -> String {
92 format!(
94 "Deployment {} - {}",
95 self.id.as_str().bold(),
96 self.state.to_string_colored(),
97 )
98 }
99 pub fn to_string_colored(&self) -> String {
100 format!(
102 "Deployment {} - {}\n{}",
103 self.id.as_str().bold(),
104 self.state.to_string_colored(),
105 self.uris.join("\n"),
106 )
107 }
108}
109
110#[derive(Deserialize, Serialize)]
111#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
112#[typeshare::typeshare]
113pub struct UploadArchiveResponse {
114 pub archive_version_id: String,
116}
117
118#[derive(Deserialize, Serialize)]
119#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
120#[serde(tag = "type", content = "content")]
121#[typeshare::typeshare]
122pub enum DeploymentRequest {
123 BuildArchive(DeploymentRequestBuildArchive),
125 Image(DeploymentRequestImage),
128}
129
130#[derive(Default, Deserialize, Serialize)]
131#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
132#[typeshare::typeshare]
133pub struct DeploymentRequestBuildArchive {
134 pub archive_version_id: String,
136 pub build_args: Option<BuildArgs>,
137 pub secrets: Option<HashMap<String, String>>,
140 pub build_meta: Option<BuildMeta>,
141}
142
143#[derive(Deserialize, Serialize, Default)]
144#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
145#[serde(tag = "type", content = "content")]
146#[typeshare::typeshare]
147pub enum BuildArgs {
148 Rust(BuildArgsRust),
149 #[default]
150 Unknown,
151}
152
153#[derive(Deserialize, Serialize)]
154#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
155#[typeshare::typeshare]
156pub struct BuildArgsRust {
157 pub shuttle_runtime_version: Option<String>,
159 pub cargo_chef: bool,
161 pub cargo_build: bool,
163 pub package_name: Option<String>,
165 pub binary_name: Option<String>,
167 pub features: Option<String>,
169 pub no_default_features: bool,
171 pub mold: bool,
173}
174
175impl Default for BuildArgsRust {
176 fn default() -> Self {
177 Self {
178 shuttle_runtime_version: Default::default(),
179 cargo_chef: true,
180 cargo_build: true,
181 package_name: Default::default(),
182 binary_name: Default::default(),
183 features: Default::default(),
184 no_default_features: Default::default(),
185 mold: Default::default(),
186 }
187 }
188}
189
190pub const GIT_STRINGS_MAX_LENGTH: usize = 80;
192
193#[derive(Default, Deserialize, Serialize)]
194#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
195#[typeshare::typeshare]
196pub struct BuildMeta {
197 pub git_commit_id: Option<String>,
198 pub git_commit_msg: Option<String>,
199 pub git_branch: Option<String>,
200 pub git_dirty: Option<bool>,
201}
202
203#[cfg(feature = "display")]
204impl std::fmt::Display for BuildMeta {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 if let Some(true) = self.git_dirty {
207 write!(f, "(dirty) ")?;
208 }
209 if let Some(ref c) = self.git_commit_id {
210 write!(f, "[{}] ", c.chars().take(7).collect::<String>())?;
211 }
212 if let Some(ref m) = self.git_commit_msg {
213 write!(f, "{m}")?;
214 }
215
216 Ok(())
217 }
218}
219
220#[derive(Default, Deserialize, Serialize)]
221#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
222#[typeshare::typeshare]
223pub struct DeploymentRequestImage {
224 pub image: String,
225 pub secrets: Option<HashMap<String, String>>,
227 }
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct DeploymentMetadata {
232 pub env: Environment,
233 pub project_name: String,
234 pub storage_path: PathBuf,
236}
237
238#[derive(
240 Clone, Copy, Debug, Default, Display, EnumString, PartialEq, Eq, Serialize, Deserialize,
241)]
242#[serde(rename_all = "lowercase")]
243#[strum(serialize_all = "lowercase")]
244pub enum Environment {
245 #[default]
246 Local,
247 #[strum(serialize = "production")] Deployment,
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use std::str::FromStr;
255
256 #[test]
257 fn test_state_deser() {
258 assert_eq!(
259 DeploymentState::Building,
260 DeploymentState::from_str("Building").unwrap()
261 );
262 assert_eq!(
263 DeploymentState::Building,
264 DeploymentState::from_str("BuilDing").unwrap()
265 );
266 assert_eq!(
267 DeploymentState::Building,
268 DeploymentState::from_str("building").unwrap()
269 );
270 }
271
272 #[test]
273 fn test_env_deser() {
274 assert_eq!(Environment::Local, Environment::from_str("local").unwrap());
275 assert_eq!(
276 Environment::Deployment,
277 Environment::from_str("production").unwrap()
278 );
279 assert!(Environment::from_str("somewhere_else").is_err());
280 assert_eq!(format!("{:?}", Environment::Local), "Local".to_owned());
281 assert_eq!(format!("{}", Environment::Local), "local".to_owned());
282 assert_eq!(Environment::Local.to_string(), "local".to_owned());
283 }
284}