Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(gateway): more descriptive project not found error #1452

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 7 additions & 5 deletions common/src/models/error.rs
Expand Up @@ -39,7 +39,7 @@ pub enum ErrorKind {
Forbidden,
UserNotFound,
UserAlreadyExists,
ProjectNotFound,
ProjectNotFound(String),
InvalidProjectName(InvalidProjectName),
ProjectAlreadyExists,
/// Contains a message describing a running state of the project.
Expand Down Expand Up @@ -77,10 +77,12 @@ impl From<ErrorKind> for ApiError {
ErrorKind::BadHost => (StatusCode::BAD_REQUEST, "The 'Host' header is invalid"),
ErrorKind::UserNotFound => (StatusCode::NOT_FOUND, "User not found"),
ErrorKind::UserAlreadyExists => (StatusCode::BAD_REQUEST, "User already exists"),
ErrorKind::ProjectNotFound => (
StatusCode::NOT_FOUND,
"Project not found. Make sure you are the owner of this project name. Run `cargo shuttle project start` to create a new project.",
),
ErrorKind::ProjectNotFound(project_name) => {
return Self {
message: format!("Project '{}' not found. Make sure you are the owner of this project name. Run `cargo shuttle project start` to create a new project.", project_name),
status_code: StatusCode::NOT_FOUND.as_u16(),
}
},
ErrorKind::ProjectNotReady => (
StatusCode::SERVICE_UNAVAILABLE,
// "not ready" is matched against in cargo-shuttle for giving further instructions on project deletion
Expand Down
2 changes: 1 addition & 1 deletion gateway/src/auth.rs
Expand Up @@ -87,7 +87,7 @@ where
if user.projects.contains(&scope) || user.claim.scopes.contains(&Scope::Admin) {
Ok(Self { user, scope })
} else {
Err(Error::from(ErrorKind::ProjectNotFound))
Err(Error::from(ErrorKind::ProjectNotFound(scope.to_string())))
}
}
}
36 changes: 19 additions & 17 deletions gateway/src/proxy.rs
Expand Up @@ -23,6 +23,7 @@ use once_cell::sync::Lazy;
use opentelemetry::global;
use opentelemetry_http::HeaderInjector;
use shuttle_common::backends::headers::XShuttleProject;
use shuttle_common::models::error::InvalidProjectName;
use tokio::sync::mpsc::Sender;
use tower::{Service, ServiceBuilder};
use tower_sanitize_path::SanitizePath;
Expand Down Expand Up @@ -110,23 +111,24 @@ impl UserProxy {
.headers()
.typed_get::<Host>()
.map(|host| fqdn!(host.hostname()))
.ok_or_else(|| Error::from_kind(ErrorKind::ProjectNotFound))?;

let project_name =
if fqdn.is_subdomain_of(&self.public) && fqdn.depth() - self.public.depth() == 1 {
fqdn.labels()
.next()
.unwrap()
.to_owned()
.parse()
.map_err(|_| Error::from_kind(ErrorKind::ProjectNotFound))?
} else if let Ok(CustomDomain { project_name, .. }) =
self.gateway.project_details_for_custom_domain(&fqdn).await
{
project_name
} else {
return Err(Error::from_kind(ErrorKind::ProjectNotFound));
};
.ok_or_else(|| Error::from_kind(ErrorKind::BadHost))?;

let project_name = if fqdn.is_subdomain_of(&self.public)
&& fqdn.depth() - self.public.depth() == 1
{
fqdn.labels()
.next()
.unwrap()
.to_owned()
.parse()
.map_err(|_| Error::from_kind(ErrorKind::InvalidProjectName(InvalidProjectName)))?
} else if let Ok(CustomDomain { project_name, .. }) =
self.gateway.project_details_for_custom_domain(&fqdn).await
{
project_name
} else {
return Err(Error::from_kind(ErrorKind::CustomDomainNotFound));
};

req.headers_mut()
.typed_insert(XShuttleProject(project_name.to_string()));
Expand Down
8 changes: 4 additions & 4 deletions gateway/src/service.rs
Expand Up @@ -388,7 +388,7 @@ impl GatewayService {
))
}),
})
.ok_or_else(|| Error::from_kind(ErrorKind::ProjectNotFound))
.ok_or_else(|| Error::from_kind(ErrorKind::ProjectNotFound(project_name.to_string())))
}

pub async fn project_name_exists(&self, project_name: &ProjectName) -> Result<bool, Error> {
Expand Down Expand Up @@ -472,7 +472,7 @@ impl GatewayService {
.fetch_optional(&self.db)
.await?
.map(|row| row.get("account_name"))
.ok_or_else(|| Error::from(ErrorKind::ProjectNotFound))
.ok_or_else(|| Error::from(ErrorKind::ProjectNotFound(project_name.to_string())))
}

pub async fn control_key_from_project_name(
Expand All @@ -484,7 +484,7 @@ impl GatewayService {
.fetch_optional(&self.db)
.await?
.map(|row| row.try_get("initial_key").unwrap())
.ok_or_else(|| Error::from(ErrorKind::ProjectNotFound))?;
.ok_or_else(|| Error::from(ErrorKind::ProjectNotFound(project_name.to_string())))?;
Ok(control_key)
}

Expand Down Expand Up @@ -1266,7 +1266,7 @@ pub mod tests {
assert!(matches!(
svc.find_project(&matrix).await,
Err(Error {
kind: ErrorKind::ProjectNotFound,
kind: ErrorKind::ProjectNotFound(_),
..
})
));
Expand Down