1use crate::environment::EnvironmentErrorMessage;
2use std::process::ExitStatus;
3
4#[cfg(not(any(target_os = "windows", test, feature = "test-support")))]
5use {collections::HashMap, std::path::Path, util::ResultExt};
6
7#[derive(Clone)]
8pub enum DirenvError {
9 NotFound,
10 FailedRun,
11 NonZeroExit(ExitStatus, Vec<u8>),
12 InvalidJson,
13}
14
15impl From<DirenvError> for Option<EnvironmentErrorMessage> {
16 fn from(value: DirenvError) -> Self {
17 match value {
18 DirenvError::NotFound => None,
19 DirenvError::FailedRun | DirenvError::NonZeroExit(_, _) => {
20 Some(EnvironmentErrorMessage(String::from(
21 "Failed to run direnv. See logs for more info",
22 )))
23 }
24 DirenvError::InvalidJson => Some(EnvironmentErrorMessage(String::from(
25 "Direnv returned invalid json. See logs for more info",
26 ))),
27 }
28 }
29}
30
31#[cfg(not(any(target_os = "windows", test, feature = "test-support")))]
32pub async fn load_direnv_environment(
33 env: &HashMap<String, String>,
34 dir: &Path,
35) -> Result<HashMap<String, Option<String>>, DirenvError> {
36 let Ok(direnv_path) = which::which("direnv") else {
37 return Err(DirenvError::NotFound);
38 };
39
40 let args = &["export", "json"];
41 let Some(direnv_output) = smol::process::Command::new(&direnv_path)
42 .args(args)
43 .envs(env)
44 .env("TERM", "dumb")
45 .current_dir(dir)
46 .output()
47 .await
48 .log_err()
49 else {
50 return Err(DirenvError::FailedRun);
51 };
52
53 if !direnv_output.status.success() {
54 log::error!(
55 "Loading direnv environment failed ({}), stderr: {}",
56 direnv_output.status,
57 String::from_utf8_lossy(&direnv_output.stderr)
58 );
59 return Err(DirenvError::NonZeroExit(
60 direnv_output.status,
61 direnv_output.stderr,
62 ));
63 }
64
65 let output = String::from_utf8_lossy(&direnv_output.stdout);
66 if output.is_empty() {
67 // direnv outputs nothing when it has no changes to apply to environment variables
68 return Ok(HashMap::default());
69 }
70
71 match serde_json::from_str(&output) {
72 Ok(env) => Ok(env),
73 Err(err) => {
74 log::error!(
75 "json parse error {}, while parsing output of `{} {}`:\n{}",
76 err,
77 direnv_path.display(),
78 args.join(" "),
79 output
80 );
81 Err(DirenvError::InvalidJson)
82 }
83 }
84}