mmrbi/
env.rs

1//! Similar to [std::env::*](mod@std::env), but optimized for better error messages.
2//!
3//! | Prefix        | Set           | Unset     | Set (Invalid Unicode) |
4//! | ------------- | ------------- | --------- | --------------------- |
5//! | var_str       | Ok(value)     | Err       | Err
6//! | var_lossy     | Ok(value)     | Err       | Ok(**lossy**)
7//! | var_os        | Ok(value)     | Err       | Ok(value)
8//! | var_path      | Ok(value)     | Err       | Ok(value)
9//! | req_var_str   | value         | <span style="color: red; font-weight: bold">exit</span>      | <span style="color: red; font-weight: bold">exit</span>
10//! | req_var_lossy | value         | <span style="color: red; font-weight: bold">exit</span>      | **lossy**
11//! | req_var_os    | value         | <span style="color: red; font-weight: bold">exit</span>      | value
12//! | req_var_path  | value         | <span style="color: red; font-weight: bold">exit</span>      | value
13//! | opt_var_str   | Some(value)   | None      | <span style="color: red; font-weight: bold">exit</span>
14//! | opt_var_lossy | Some(value)   | None      | Some(**lossy**)
15//! | opt_var_os    | Some(value)   | None      | Some(value)
16//! | opt_var_path  | Some(value)   | None      | Some(value)
17
18use crate::*;
19
20use std::ffi::{OsStr, OsString};
21use std::fmt::{self, Display, Formatter};
22use std::path::{Path, PathBuf};
23
24
25
26pub type Result<T> = std::result::Result<T, Error>;
27
28/// Contextual env var error.  Examples:
29/// <code style="display: block; padding: 0.25em; margin: 0.5em 0;">%NONEXISTANT% is not set   <span style="color: #888">(windows)</span>
30/// ${NONEXISTANT} is not set  <span style="color: #888">(linux)</span></code>
31
32#[derive(Clone, Debug)]
33#[non_exhaustive]
34pub enum Error {
35    NotSet(OsString),
36    InvalidUnicode(OsString),
37}
38
39impl Display for Error {
40    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
41        fn display<'a>(var: &'a OsStr) -> impl Display + 'a { Path::new(var).display() }
42
43        if cfg!(windows) {
44            match self {
45                Error::NotSet(var)          => write!(fmt, "%{}% is not set",               display(var)),
46                Error::InvalidUnicode(var)  => write!(fmt, "%{}% contains invalid unicode", display(var)),
47            }
48        } else {
49            match self {
50                Error::NotSet(var)          => write!(fmt, "${{{}}} is not set",               display(var)),
51                Error::InvalidUnicode(var)  => write!(fmt, "${{{}}} contains invalid unicode", display(var)),
52            }
53        }
54    }
55}
56
57
58
59pub fn has_var(name: impl AsRef<OsStr> + Into<OsString>) -> bool {
60    std::env::var_os(name).is_some()
61}
62
63pub fn var_str(name: impl AsRef<OsStr> + Into<OsString>) -> Result<String> {
64    match std::env::var(name.as_ref()) {
65        Ok(v) => Ok(v),
66        Err(std::env::VarError::NotPresent)     => Err(Error::NotSet(name.into())),
67        Err(std::env::VarError::NotUnicode(_))  => Err(Error::InvalidUnicode(name.into())),
68    }
69}
70
71pub fn var_lossy(name: impl AsRef<OsStr> + Into<OsString>) -> Result<String> {
72    match std::env::var_os(name.as_ref()) {
73        Some(v) => Ok(into_string_lossy(v)),
74        None    => Err(Error::NotSet(name.into())),
75    }
76}
77
78pub fn var_os(name: impl AsRef<OsStr> + Into<OsString>) -> Result<OsString> {
79    match std::env::var_os(name.as_ref()) {
80        Some(v) => Ok(v),
81        None    => Err(Error::NotSet(name.into())),
82    }
83}
84
85pub fn var_path(name: impl AsRef<OsStr> + Into<OsString>) -> Result<PathBuf> {
86    match std::env::var_os(name.as_ref()) {
87        Some(v) => Ok(PathBuf::from(v)),
88        None    => Err(Error::NotSet(name.into())),
89    }
90}
91
92
93
94pub fn req_var_str  (name: impl AsRef<OsStr> + Into<OsString>) -> String    { var_str(name).or_die() }
95pub fn req_var_lossy(name: impl AsRef<OsStr> + Into<OsString>) -> String    { var_lossy(name).or_die() }
96pub fn req_var_os   (name: impl AsRef<OsStr> + Into<OsString>) -> OsString  { var_os(name).or_die() }
97pub fn req_var_path (name: impl AsRef<OsStr> + Into<OsString>) -> PathBuf   { var_path(name).or_die() }
98
99
100
101pub fn opt_var_str(name: impl AsRef<OsStr> + Into<OsString>) -> Result<Option<String>> {
102    match std::env::var(name.as_ref()) {
103        Ok(v) => Ok(Some(v)),
104        Err(std::env::VarError::NotPresent)     => Ok(None),
105        Err(std::env::VarError::NotUnicode(_))  => Err(Error::InvalidUnicode(name.into())),
106    }
107}
108
109pub fn opt_var_lossy(name: impl AsRef<OsStr> + Into<OsString>) -> Option<String> {
110    std::env::var_os(name.as_ref()).map(into_string_lossy)
111}
112
113pub fn opt_var_os(name: impl AsRef<OsStr> + Into<OsString>) -> Option<OsString> {
114    std::env::var_os(name.as_ref())
115}
116
117pub fn opt_var_path(name: impl AsRef<OsStr> + Into<OsString>) -> Option<PathBuf> {
118    std::env::var_os(name.as_ref()).map(PathBuf::from)
119}
120
121
122
123fn into_string_lossy(os: OsString) -> String {
124    // Optimized for the common case where OsString is valid UTF8
125    os.into_string().map_or_else(
126        |os| os.to_string_lossy().into_owned(), // could be optimized to not revalidate early unicode
127        |s| s,
128    )
129}