Skip to content
27 changes: 14 additions & 13 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ pub fn handle_command_execution_result<T: Formatter>(
)
}
_ => {
error!("Error occurred: {e:?}");
error!("Error occurred: {e}");
}
}
std::process::exit(1);
Expand Down Expand Up @@ -565,7 +565,7 @@ pub fn handle_cli(cli: &Cli) {
std::process::exit(1);
}
_ => {
error!("Error occurred: {e:?}");
error!("Error occurred: {e}");
std::process::exit(1);
}
},
Expand Down Expand Up @@ -657,7 +657,7 @@ pub fn handle_cli_screen_command(command: &ScreenCommands, output: OutputFormat)
std::process::exit(0);
}
Err(e) => {
error!("Error occurred: {e:?}");
error!("Error occurred: {e}");
std::process::exit(1);
}
}
Expand Down Expand Up @@ -685,7 +685,7 @@ pub fn handle_cli_playlist_command(command: &PlaylistCommands, output: OutputFor
println!("Playlist deleted successfully.");
}
Err(e) => {
eprintln!("Error occurred when deleting playlist: {e:?}")
eprintln!("Error occurred when deleting playlist: {e}")
}
},
PlaylistCommands::Append {
Expand Down Expand Up @@ -729,7 +729,7 @@ pub fn handle_cli_playlist_command(command: &PlaylistCommands, output: OutputFor
println!("Playlist updated successfully.");
}
Err(e) => {
eprintln!("Error occurred when updating playlist: {e:?}")
eprintln!("Error occurred when updating playlist: {e}")
}
}
}
Expand Down Expand Up @@ -783,7 +783,7 @@ pub fn handle_cli_asset_command(command: &AssetCommands, output: OutputFormat) {
std::process::exit(0);
}
Err(e) => {
error!("Error occurred: {e:?}");
error!("Error occurred: {e}");
std::process::exit(1);
}
}
Expand Down Expand Up @@ -818,7 +818,7 @@ pub fn handle_cli_asset_command(command: &AssetCommands, output: OutputFormat) {
info!("Asset updated successfully.");
}
Err(e) => {
error!("Error occurred: {e:?}");
error!("Error occurred: {e}");
std::process::exit(1);
}
}
Expand All @@ -829,7 +829,7 @@ pub fn handle_cli_asset_command(command: &AssetCommands, output: OutputFormat) {
info!("Asset updated successfully.");
}
Err(e) => {
error!("Error occurred: {e:?}");
error!("Error occurred: {e}");
std::process::exit(1);
}
}
Expand All @@ -844,7 +844,7 @@ pub fn handle_cli_asset_command(command: &AssetCommands, output: OutputFormat) {
info!("Asset updated successfully.");
}
Err(e) => {
error!("Error occurred: {e:?}");
error!("Error occurred: {e}");
std::process::exit(1);
}
}
Expand All @@ -855,7 +855,7 @@ pub fn handle_cli_asset_command(command: &AssetCommands, output: OutputFormat) {
info!("Asset updated successfully.");
}
Err(e) => {
error!("Error occurred: {e:?}");
error!("Error occurred: {e}");
std::process::exit(1);
}
}
Expand All @@ -869,7 +869,7 @@ pub fn handle_cli_asset_command(command: &AssetCommands, output: OutputFormat) {
info!("Asset updated successfully.");
}
Err(e) => {
error!("Error occurred: {e:?}");
error!("Error occurred: {e}");
std::process::exit(1);
}
}
Expand Down Expand Up @@ -999,7 +999,7 @@ pub fn handle_cli_edge_app_command(command: &EdgeAppCommands, output: OutputForm
std::process::exit(0);
}
Err(e) => {
error!("Error occurred: {e:?}");
error!("Error occurred: {e}");
std::process::exit(1);
}
}
Expand Down Expand Up @@ -1247,8 +1247,9 @@ mod tests {
let mock_server = MockServer::start();
mock_server.mock(|when, then| {
when.method(GET)
.path("/v4/screens")
.path("/v4.1/screens")
.query_param("id", "eq.017a5104-524b-33d8-8026-9087b59e7eb5")
.query_param("select", "*,screens_configs(*),screens_pings(*),screens_reports(*),screens_statuses(*)")
.header("user-agent", format!("screenly-cli {}", env!("CARGO_PKG_VERSION")))
.header("Authorization", "Token token");
then
Expand Down
68 changes: 59 additions & 9 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,10 @@ pub enum CommandError {
Parse(#[from] serde_json::Error),
#[error("parse error: {0}")]
YamlParse(#[from] serde_yaml::Error),
#[error("unknown error: {0}")]
#[error("unexpected response status: {0}")]
WrongResponseStatus(u16),
#[error("{0}")]
ApiError(String),
#[error("Required field is missing in the response")]
MissingField,
#[error("Required file is missing in the edge app directory: {0}")]
Expand Down Expand Up @@ -170,6 +172,17 @@ pub enum CommandError {
AppNotFound(String),
}

fn api_error_from_body(body: &str, status: StatusCode) -> CommandError {
if let Ok(json) = serde_json::from_str::<serde_json::Value>(body) {
for key in &["error", "message", "detail"] {
if let Some(msg) = json.get(key).and_then(|v| v.as_str()) {
return CommandError::ApiError(msg.to_string());
}
}
}
CommandError::WrongResponseStatus(status.as_u16())
}

pub fn get(
authentication: &Authentication,
endpoint: &str,
Expand All @@ -189,8 +202,9 @@ pub fn get(
debug!("GET {url} -> {status}");

if status != StatusCode::OK {
println!("Response: {:?}", &response.text());
return Err(CommandError::WrongResponseStatus(status.as_u16()));
let body = response.text().unwrap_or_default();
debug!("Response: {:?}", &body);
return Err(api_error_from_body(&body, status));
}
Ok(serde_json::from_str(&response.text()?)?)
}
Expand All @@ -216,8 +230,9 @@ pub fn post<T: Serialize + ?Sized>(

// Ok, No_Content are acceptable because some of our RPC code returns that.
if ![StatusCode::CREATED, StatusCode::OK, StatusCode::NO_CONTENT].contains(&status) {
debug!("Response: {:?}", &response.text()?);
return Err(CommandError::WrongResponseStatus(status.as_u16()));
let body = response.text().unwrap_or_default();
debug!("Response: {:?}", &body);
return Err(api_error_from_body(&body, status));
}
if status == StatusCode::NO_CONTENT {
return Ok(serde_json::Value::Null);
Expand All @@ -233,8 +248,9 @@ pub fn delete(authentication: &Authentication, endpoint: &str) -> anyhow::Result
let status = response.status();

if ![StatusCode::OK, StatusCode::NO_CONTENT].contains(&status) {
debug!("Response: {:?}", &response.text()?);
return Err(CommandError::WrongResponseStatus(status.as_u16()));
let body = response.text().unwrap_or_default();
debug!("Response: {:?}", &body);
return Err(api_error_from_body(&body, status));
}
Ok(())
}
Expand All @@ -257,8 +273,9 @@ pub fn patch<T: Serialize + ?Sized>(

let status = response.status();
if status != StatusCode::OK {
debug!("Response: {:?}", &response.text()?);
return Err(CommandError::WrongResponseStatus(status.as_u16()));
let body = response.text().unwrap_or_default();
debug!("Response: {:?}", &body);
return Err(api_error_from_body(&body, status));
}

if status == StatusCode::NO_CONTENT {
Expand Down Expand Up @@ -659,8 +676,41 @@ impl Formatter for PlaylistItems {

#[cfg(test)]
mod tests {
use reqwest::StatusCode;

use super::*;

#[test]
fn test_api_error_from_body_uses_error_key() {
let err = api_error_from_body(r#"{"error": "Invalid pin"}"#, StatusCode::BAD_REQUEST);
assert_eq!(err.to_string(), "Invalid pin");
}

#[test]
fn test_api_error_from_body_uses_message_key() {
let err = api_error_from_body(r#"{"message": "Not found"}"#, StatusCode::NOT_FOUND);
assert_eq!(err.to_string(), "Not found");
}

#[test]
fn test_api_error_from_body_uses_detail_key() {
let err = api_error_from_body(r#"{"detail": "Permission denied"}"#, StatusCode::FORBIDDEN);
assert_eq!(err.to_string(), "Permission denied");
}

#[test]
fn test_api_error_from_body_falls_back_to_status_on_unknown_key() {
let err =
api_error_from_body(r#"{"code": "ERR123"}"#, StatusCode::INTERNAL_SERVER_ERROR);
assert_eq!(err.to_string(), "unexpected response status: 500");
}

#[test]
fn test_api_error_from_body_falls_back_to_status_on_non_json() {
let err = api_error_from_body("not json", StatusCode::BAD_GATEWAY);
assert_eq!(err.to_string(), "unexpected response status: 502");
}

#[test]
fn test_assets_csv_round_trip() {
let data = r#"[
Expand Down
Loading
Loading