Skip to content

Commit 728881d

Browse files
feat(rover): adds suggestions for HoustonProblem errors
1 parent 6fbd736 commit 728881d

File tree

7 files changed

+102
-26
lines changed

7 files changed

+102
-26
lines changed

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ repository = "https://github.com/apollographql/rover/"
88
[dependencies]
99

1010
# workspace deps
11+
binstall = { path = "./installers/binstall" }
1112
houston = { path = "./crates/houston" }
13+
robot-panic = { path = "./crates/robot-panic" }
1214
rover-client = { path = "./crates/rover-client" }
1315
sputnik = { path = "./crates/sputnik" }
1416
timber = { path = "./crates/timber" }
15-
robot-panic = { path = "./crates/robot-panic" }
16-
binstall = { path = "./installers/binstall" }
1717

1818
# crates.io deps
1919
anyhow = "1.0.38"

crates/houston/src/config.rs

+24-13
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,32 @@ impl Config {
2626
override_api_key: Option<String>,
2727
) -> Result<Config, HoustonProblem> {
2828
let home = match override_home {
29-
Some(home) => PathBuf::from(home.as_ref()),
29+
Some(home) => {
30+
let home_path = PathBuf::from(home.as_ref());
31+
if home_path.exists() && !home_path.is_dir() {
32+
Err(HoustonProblem::InvalidOverrideConfigDir(
33+
home_path.display().to_string(),
34+
))
35+
} else {
36+
Ok(home_path)
37+
}
38+
}
3039
None => {
3140
// Lin: /home/alice/.config/rover
3241
// Win: C:\Users\Alice\AppData\Roaming\Apollo\Rover\config
3342
// Mac: /Users/Alice/Library/Application Support/com.Apollo.Rover
34-
ProjectDirs::from("com", "Apollo", "Rover")
35-
.ok_or(HoustonProblem::ConfigDirNotFound)?
43+
Ok(ProjectDirs::from("com", "Apollo", "Rover")
44+
.ok_or(HoustonProblem::DefaultConfigDirNotFound)?
3645
.config_dir()
37-
.to_path_buf()
46+
.to_path_buf())
3847
}
39-
};
48+
}?;
49+
50+
if !home.exists() {
51+
fs::create_dir_all(&home).map_err(|_| {
52+
HoustonProblem::CouldNotCreateConfigHome(home.display().to_string())
53+
})?;
54+
}
4055

4156
Ok(Config {
4257
home,
@@ -47,7 +62,8 @@ impl Config {
4762
/// Removes all configuration files from filesystem
4863
pub fn clear(&self) -> Result<(), HoustonProblem> {
4964
tracing::debug!(home_dir = ?self.home);
50-
fs::remove_dir_all(&self.home).map_err(|_| HoustonProblem::NoConfigFound)
65+
fs::remove_dir_all(&self.home)
66+
.map_err(|_| HoustonProblem::NoConfigFound(self.home.display().to_string()))
5167
}
5268
}
5369

@@ -57,15 +73,10 @@ mod tests {
5773
use assert_fs::TempDir;
5874
#[test]
5975
fn it_can_clear_global_config() {
60-
let config = get_config(None);
61-
std::fs::create_dir(&config.home).unwrap();
76+
let tmp_home = TempDir::new().unwrap();
77+
let config = Config::new(Some(&tmp_home.path()), None).unwrap();
6278
assert!(config.home.exists());
6379
config.clear().unwrap();
6480
assert_eq!(config.home.exists(), false);
6581
}
66-
67-
fn get_config(override_api_key: Option<String>) -> Config {
68-
let tmp_home = TempDir::new().unwrap();
69-
Config::new(Some(&tmp_home.path()), override_api_key).unwrap()
70-
}
7182
}

crates/houston/src/error.rs

+14-6
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,27 @@ use std::io;
66
#[derive(Error, Debug)]
77
pub enum HoustonProblem {
88
/// ConfigDirNotFound occurs when the default OS config can't be found.
9-
#[error("Could not determine default OS config directory.")]
10-
ConfigDirNotFound,
9+
#[error("Could not determine default OS configuration directory.")]
10+
DefaultConfigDirNotFound,
11+
12+
/// CouldNotCreateConfig occurs when a configuration directory could not be created.
13+
#[error("Could not create a configuration directory at \"{0}\".")]
14+
CouldNotCreateConfigHome(String),
15+
16+
/// InvalidOverrideConfigDir occurs when a user provides a path to a non-directory.
17+
#[error("\"{0}\" already exists and is not a directory.")]
18+
InvalidOverrideConfigDir(String),
1119

1220
/// NoConfigFound occurs when a global configuration directory can't be found.
13-
#[error("Could not find a global configuration directory.")]
14-
NoConfigFound,
21+
#[error("Could not find a configuration directory at \"{0}\".")]
22+
NoConfigFound(String),
1523

1624
/// ProfileNotFound occurs when a profile with a specified name can't be found.
17-
#[error("There is no profile named \"{0}\"")]
25+
#[error("There is no profile named \"{0}\".")]
1826
ProfileNotFound(String),
1927

2028
/// NoNonSensitiveConfigFound occurs when non-sensitive config can't be found for a profile.
21-
#[error("No non-sensitive config found for profile \"{0}\"")]
29+
#[error("No non-sensitive config found for profile \"{0}\".")]
2230
NoNonSensitiveConfigFound(String),
2331

2432
/// TomlSerialization occurs when a profile's configuration can't be serialized to a String.

src/error/metadata/code.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::fmt::{self, Display};
22

3+
/// `Code` contains the error codes associated with specific errors.
34
#[derive(Debug)]
45
pub enum Code {}
56

src/error/metadata/mod.rs

+28-1
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,22 @@ use suggestion::Suggestion;
77
use houston::HoustonProblem;
88
use rover_client::RoverClientError;
99

10+
use crate::env::RoverEnvKey;
11+
12+
use std::env;
13+
14+
/// Metadata contains extra information about specific errors
15+
/// Currently this includes an optional error `Code`
16+
/// and an optional `Suggestion`
1017
#[derive(Default, Debug)]
1118
pub struct Metadata {
1219
pub suggestion: Option<Suggestion>,
1320
pub code: Option<Code>,
1421
}
1522

23+
/// `Metadata` structs can be created from an `anyhow::Error`
24+
/// This works by downcasting the errors to their underlying types
25+
/// and creating `Suggestion`s and `Code`s where applicable
1626
impl From<&mut anyhow::Error> for Metadata {
1727
fn from(error: &mut anyhow::Error) -> Self {
1828
if let Some(rover_client_error) = error.downcast_ref::<RoverClientError>() {
@@ -33,7 +43,24 @@ impl From<&mut anyhow::Error> for Metadata {
3343
HoustonProblem::NoNonSensitiveConfigFound(_) => {
3444
(Some(Suggestion::RerunWithSensitive), None)
3545
}
36-
_ => (None, None),
46+
HoustonProblem::CouldNotCreateConfigHome(_)
47+
| HoustonProblem::DefaultConfigDirNotFound
48+
| HoustonProblem::InvalidOverrideConfigDir(_) => {
49+
(Some(Suggestion::SetConfigHome), None)
50+
}
51+
HoustonProblem::NoConfigFound(_) => {
52+
let code = None;
53+
let suggestion = if env::var_os(RoverEnvKey::ConfigHome.to_string()).is_some() {
54+
Some(Suggestion::MigrateConfigHomeOrCreateConfig)
55+
} else {
56+
Some(Suggestion::CreateConfig)
57+
};
58+
(suggestion, code)
59+
}
60+
HoustonProblem::ProfileNotFound(_) => (Some(Suggestion::ListProfiles), None),
61+
HoustonProblem::TomlDeserialization(_)
62+
| HoustonProblem::TomlSerialization(_)
63+
| HoustonProblem::IOError(_) => (Some(Suggestion::SubmitIssue), None),
3764
};
3865
return Metadata { suggestion, code };
3966
}

src/error/metadata/suggestion.rs

+31
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@ use std::fmt::{self, Display};
22

33
use ansi_term::Colour::{Cyan, Yellow};
44

5+
use crate::env::RoverEnvKey;
6+
7+
/// `Suggestion` contains possible suggestions for remedying specific errors.
58
#[derive(Debug)]
69
pub enum Suggestion {
710
SubmitIssue,
811
RerunWithSensitive,
12+
SetConfigHome,
13+
MigrateConfigHomeOrCreateConfig,
14+
CreateConfig,
15+
ListProfiles,
916
}
1017

1118
impl Display for Suggestion {
@@ -20,6 +27,30 @@ impl Display for Suggestion {
2027
Yellow.normal().paint("'--sensitive'")
2128
)
2229
}
30+
Suggestion::SetConfigHome => {
31+
format!(
32+
"You can override this path by setting the {} environment variable.",
33+
Yellow.normal().paint(RoverEnvKey::ConfigHome.to_string())
34+
)
35+
}
36+
Suggestion::MigrateConfigHomeOrCreateConfig => {
37+
format!("If you've recently changed the {} environment variable, you may need to migrate your old configuration directory to the new path. Otherwise, try setting up a new configuration profile by running {}.",
38+
Yellow.normal().paint(RoverEnvKey::ConfigHome.to_string()),
39+
Yellow.normal().paint("`rover config auth`"))
40+
}
41+
Suggestion::CreateConfig => {
42+
format!(
43+
"Try setting up a configuration profile by running {}",
44+
Yellow.normal().paint("`rover config auth`")
45+
)
46+
}
47+
Suggestion::ListProfiles => {
48+
format!(
49+
"Try running {} to see the possible values for the {} argument.",
50+
Yellow.normal().paint("`rover config list`"),
51+
Yellow.normal().paint("'--profile'")
52+
)
53+
}
2354
};
2455
write!(formatter, "{}", &suggestion)
2556
}

tests/config/profile.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use assert_cmd::Command;
22
use assert_fs::TempDir;
33
use predicates::prelude::*;
4-
use serial_test::serial;
54
use std::path::PathBuf;
65

76
use houston::{Config, Profile};
@@ -11,22 +10,21 @@ const CUSTOM_PROFILE: &str = "custom-profile";
1110
const CUSTOM_API_KEY: &str = "custom-api-key";
1211

1312
#[test]
14-
#[serial]
1513
fn it_can_list_no_profiles() {
14+
let temp_dir = get_temp_dir();
1615
let mut cmd = Command::cargo_bin("rover").unwrap();
1716
let result = cmd
1817
.arg("config")
1918
.env(
2019
RoverEnvKey::ConfigHome.to_string(),
21-
get_temp_dir().to_string_lossy().to_string(),
20+
temp_dir.to_string_lossy().to_string(),
2221
)
2322
.arg("list")
2423
.assert();
2524
result.stderr(predicate::str::contains("No profiles"));
2625
}
2726

2827
#[test]
29-
#[serial]
3028
fn it_can_list_one_profile() {
3129
let temp_dir = get_temp_dir();
3230
let config = Config::new(Some(temp_dir.clone()).as_ref(), None).unwrap();

0 commit comments

Comments
 (0)