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

feat(cargo-shuttle): better error messages #391

Merged
merged 2 commits into from
Oct 11, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat(cargo-shuttle): better error messages
return an error message that is more relevant to the user than "failed to parse response to json" on non-200 responses
  • Loading branch information
oddgrd committed Oct 11, 2022
commit d90d32ac5b2b14e3802bb118fdac6ec9154a40ad
20 changes: 13 additions & 7 deletions cargo-shuttle/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use reqwest_retry::policies::ExponentialBackoff;
use reqwest_retry::RetryTransientMiddleware;
use serde::de::DeserializeOwned;
use serde::Deserialize;
use shuttle_common::models::{deployment, project, secret, service, user};
use shuttle_common::models::{deployment, error, project, secret, service, user};
use shuttle_common::project::ProjectName;
use shuttle_common::{ApiKey, ApiUrl, LogItem};
use tokio::net::TcpStream;
Expand Down Expand Up @@ -39,7 +39,17 @@ impl ToJson for Response {
response = std::str::from_utf8(&full).unwrap_or_default(),
"parsing response to json"
);
serde_json::from_slice(&full).context("failed to parse response to JSON")
// try to deserialize into calling function response model
match serde_json::from_slice(&full) {
Ok(res) => Ok(res),
Err(_) => {
// if that doesn't work, try to deserialize into common error type
let res: error::ApiError =
serde_json::from_slice(&full).context("failed to parse response to JSON")?;

Err(res.into())
}
}
}
}

Expand All @@ -63,7 +73,6 @@ impl Client {
.context("failed to get API key from Shuttle server")?
.to_json()
.await
.context("could not parse server json response for create project request")
}

pub async fn deploy(
Expand Down Expand Up @@ -147,7 +156,6 @@ impl Client {
.context("failed to make create project request")?
.to_json()
.await
.context("could not parse server json response for create project request")
}

pub async fn get_project(&self, project: &ProjectName) -> Result<project::Response> {
Expand All @@ -156,7 +164,7 @@ impl Client {
self.get(path).await
}

pub async fn delete_project(&self, project: &ProjectName) -> Result<()> {
pub async fn delete_project(&self, project: &ProjectName) -> Result<project::Response> {
let path = format!("/projects/{}", project.as_str());

self.delete(path).await
Expand Down Expand Up @@ -248,7 +256,6 @@ impl Client {
.context("failed to make get request")?
.to_json()
.await
.context("could not parse server json response for get request")
}

async fn post<T: Into<Body>>(
Expand Down Expand Up @@ -285,7 +292,6 @@ impl Client {
.context("failed to make delete request")?
.to_json()
.await
.context("could not parse server json response for delete request")
}

fn set_builder_auth(&self, builder: RequestBuilder) -> RequestBuilder {
Expand Down
4 changes: 2 additions & 2 deletions cargo-shuttle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,9 +396,9 @@ impl Shuttle {
}

async fn project_delete(&self, client: &Client) -> Result<()> {
client.delete_project(self.ctx.project_name()).await?;
let project = client.delete_project(self.ctx.project_name()).await?;

println!("Project has been deleted");
println!("{project}");

Ok(())
}
Expand Down
16 changes: 16 additions & 0 deletions common/src/models/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use std::fmt::{Display, Formatter};

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
pub struct ApiError {
pub message: String,
}

impl Display for ApiError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}

impl std::error::Error for ApiError {}
1 change: 1 addition & 0 deletions common/src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod deployment;
pub mod error;
pub mod project;
pub mod resource;
pub mod secret;
Expand Down
6 changes: 4 additions & 2 deletions deployer/src/handlers/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use axum::response::{IntoResponse, Response};
use axum::Json;

use serde::{ser::SerializeMap, Serialize};
use serde_json::json;
use shuttle_common::models::error::ApiError;

#[derive(thiserror::Error, Debug)]
pub enum Error {
Expand Down Expand Up @@ -47,7 +47,9 @@ impl IntoResponse for Error {
header::CONTENT_TYPE,
HeaderValue::from_static("application/json"),
)],
Json(json!({ "message": self })),
Json(ApiError {
message: self.to_string(),
}),
)
.into_response()
}
Expand Down
10 changes: 8 additions & 2 deletions gateway/src/api/latest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,16 @@ async fn delete_project(
user: User { name, .. },
}: ScopedUser,
Path(project): Path<ProjectName>,
) -> Result<(), Error> {
) -> Result<AxumJson<project::Response>, Error> {
let work = service.destroy_project(project, name).await?;

sender.send(work).await.map_err(Error::from)
let name = work.project_name.to_string();
let state = work.work.clone().into();

sender.send(work).await?;

let response = project::Response { name, state };
Ok(AxumJson(response))
}

async fn route_project(
Expand Down
10 changes: 8 additions & 2 deletions gateway/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use futures::prelude::*;
use once_cell::sync::Lazy;
use regex::Regex;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::json;
use shuttle_common::models::error::ApiError;
use tokio::sync::mpsc::error::SendError;
use tracing::error;

Expand Down Expand Up @@ -141,7 +141,13 @@ impl IntoResponse for Error {
ErrorKind::Forbidden => (StatusCode::FORBIDDEN, "forbidden"),
ErrorKind::NotReady => (StatusCode::INTERNAL_SERVER_ERROR, "service not ready"),
};
(status, Json(json!({ "error": error_message }))).into_response()
(
status,
Json(ApiError {
message: error_message.to_string(),
}),
)
.into_response()
}
}

Expand Down