diff --git a/common/src/database.rs b/common/src/database.rs index b31f373177..3d1d9f5bc0 100644 --- a/common/src/database.rs +++ b/common/src/database.rs @@ -5,7 +5,7 @@ use strum::{Display, EnumString}; #[cfg(feature = "openapi")] use utoipa::ToSchema; -#[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq, Hash)] #[serde(rename_all = "lowercase")] #[cfg_attr(feature = "openapi", derive(ToSchema))] #[cfg_attr(feature = "openapi", schema(as = shuttle_common::database::Type))] @@ -14,7 +14,7 @@ pub enum Type { Shared(SharedEngine), } -#[derive(Clone, Copy, Debug, Deserialize, Display, Serialize, EnumString, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, Display, Serialize, EnumString, Eq, PartialEq, Hash)] #[serde(rename_all = "lowercase")] #[strum(serialize_all = "lowercase")] #[cfg_attr(feature = "openapi", derive(ToSchema))] @@ -24,7 +24,7 @@ pub enum AwsRdsEngine { MariaDB, } -#[derive(Clone, Copy, Debug, Deserialize, Display, Serialize, EnumString, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, Display, Serialize, EnumString, Eq, PartialEq, Hash)] #[serde(rename_all = "lowercase")] #[strum(serialize_all = "lowercase")] #[cfg_attr(feature = "openapi", derive(ToSchema))] diff --git a/common/src/models/error.rs b/common/src/models/error.rs index 8d4a8f3407..cc9145d306 100644 --- a/common/src/models/error.rs +++ b/common/src/models/error.rs @@ -48,7 +48,7 @@ pub enum ErrorKind { OwnProjectAlreadyExists(String), ProjectNotReady, ProjectUnavailable, - ProjectHasDatabase, + ProjectHasResources(Vec), ProjectHasRunningDeployment, CustomDomainNotFound, InvalidCustomDomain, @@ -86,10 +86,13 @@ impl From for ApiError { StatusCode::FORBIDDEN, "A deployment is running. Stop it with `cargo shuttle stop` first." ), - ErrorKind::ProjectHasDatabase => ( - StatusCode::FORBIDDEN, - "Project has database resources. Use `cargo shuttle resource list` and `cargo shuttle resource delete ` to delete them." - ), + ErrorKind::ProjectHasResources(resources) => { + let resources = resources.join(", "); + return Self { + message: format!("Project has resources: {}. Use `cargo shuttle resource list` and `cargo shuttle resource delete ` to delete them.", resources), + status_code: StatusCode::FORBIDDEN.as_u16(), + } + } ErrorKind::InvalidProjectName => ( StatusCode::BAD_REQUEST, r#" diff --git a/common/src/resource.rs b/common/src/resource.rs index 8d247dcb6e..297806c1d9 100644 --- a/common/src/resource.rs +++ b/common/src/resource.rs @@ -25,7 +25,7 @@ pub struct Response { pub data: Value, } -#[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq, Hash)] #[serde(rename_all = "lowercase")] #[cfg_attr(feature = "openapi", derive(ToSchema))] #[cfg_attr(feature = "openapi", schema(as = shuttle_common::resource::Type))] diff --git a/gateway/src/api/latest.rs b/gateway/src/api/latest.rs index 019001caec..7a2fe256e6 100644 --- a/gateway/src/api/latest.rs +++ b/gateway/src/api/latest.rs @@ -1,3 +1,4 @@ +use std::collections::HashSet; use std::io::Cursor; use std::net::SocketAddr; use std::ops::Sub; @@ -361,11 +362,23 @@ async fn delete_project( let resources: Vec = serde_json::from_slice(&body_bytes) .map_err(|e| Error::source(ErrorKind::Internal, e))?; - if resources + + let resources = resources .into_iter() - .any(|s| matches!(s.r#type, shuttle_common::resource::Type::Database(_))) - { - return Err(Error::from_kind(ErrorKind::ProjectHasDatabase)); + .filter(|resource| { + matches!( + resource.r#type, + shuttle_common::resource::Type::Database(_) + | shuttle_common::resource::Type::Secrets + ) + }) + .map(|resource| resource.r#type.to_string()) + .collect::>() + .into_iter() + .collect::>(); + + if !resources.is_empty() { + return Err(Error::from_kind(ErrorKind::ProjectHasResources(resources))); } }