-
Couldn't load subscription status.
- Fork 252
feat(dotenv): Adding support for dotenv files #710
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| use std::error::Error; | ||
| use std::io::Cursor; | ||
|
|
||
| use dotenvy::from_read_iter; | ||
|
|
||
| use crate::map::Map; | ||
| use crate::value::{Value, ValueKind}; | ||
|
|
||
| pub(crate) fn parse( | ||
| uri: Option<&String>, | ||
| text: &str, | ||
| ) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> { | ||
| let mut map: Map<String, Value> = Map::new(); | ||
| let cursor = Cursor::new(text); | ||
|
|
||
| for item in from_read_iter(cursor) { | ||
| let (key, mut value) = item?; | ||
|
|
||
| let os_env_vars = std::env::vars_os(); | ||
| for (os_string_key, os_string_value) in os_env_vars { | ||
| let string_key: String = os_string_key.to_string_lossy().into_owned(); | ||
| if string_key == key { | ||
| value = os_string_value.to_string_lossy().into_owned(); | ||
| } | ||
| } | ||
|
|
||
| map.insert(key, Value::new(uri, ValueKind::String(value))); | ||
| } | ||
|
|
||
| Ok(map) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,9 @@ mod json5; | |
| #[cfg(feature = "corn")] | ||
| mod corn; | ||
|
|
||
| #[cfg(feature = "dotenv")] | ||
| mod dotenv; | ||
|
|
||
| /// File formats provided by the library. | ||
| /// | ||
| /// Although it is possible to define custom formats using [`Format`] trait it is recommended to use `FileFormat` if possible. | ||
|
|
@@ -57,6 +60,10 @@ pub enum FileFormat { | |
| /// Corn (parsed with `libcorn`) | ||
| #[cfg(feature = "corn")] | ||
| Corn, | ||
|
|
||
| /// Dotenv (parsed with `dotenvy`) | ||
| #[cfg(feature = "dotenv")] | ||
| Dotenv, | ||
|
Comment on lines
+63
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this work as another There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is another let s = Config::builder()
.add_source(config::File::from_str(
r#"
FOO=bar
BAZ=qux
"#,
config::FileFormat::Dotenv,
))
.build()
.unwrap();There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI resolving conversations that aren't ready for it can make PR reviews more difficult on maintainers. You should be pretty confident that you understood the concern and responded to it as the maintainer expected to resolve it. That was not the case here. Some traits of dotenv are
Both of these characteristics are shared with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've raised this (and other questions) at #16 (comment) as I prefer to work out high level designs in Issues rather than PRs. |
||
| } | ||
|
|
||
| impl FileFormat { | ||
|
|
@@ -76,6 +83,8 @@ impl FileFormat { | |
| FileFormat::Json5, | ||
| #[cfg(feature = "corn")] | ||
| FileFormat::Corn, | ||
| #[cfg(feature = "dotenv")] | ||
| FileFormat::Dotenv, | ||
| ] | ||
| } | ||
|
|
||
|
|
@@ -102,13 +111,17 @@ impl FileFormat { | |
| #[cfg(feature = "corn")] | ||
| FileFormat::Corn => &["corn"], | ||
|
|
||
| #[cfg(feature = "dotenv")] | ||
| FileFormat::Dotenv => &["dotenv"], | ||
|
|
||
| #[cfg(all( | ||
| not(feature = "toml"), | ||
| not(feature = "json"), | ||
| not(feature = "yaml"), | ||
| not(feature = "ini"), | ||
| not(feature = "ron"), | ||
| not(feature = "json5"), | ||
| not(feature = "dotenv"), | ||
| ))] | ||
| _ => unreachable!("No features are enabled, this library won't work without features"), | ||
| } | ||
|
|
@@ -141,13 +154,17 @@ impl FileFormat { | |
| #[cfg(feature = "corn")] | ||
| FileFormat::Corn => corn::parse(uri, text), | ||
|
|
||
| #[cfg(feature = "dotenv")] | ||
| FileFormat::Dotenv => dotenv::parse(uri, text), | ||
|
|
||
| #[cfg(all( | ||
| not(feature = "toml"), | ||
| not(feature = "json"), | ||
| not(feature = "yaml"), | ||
| not(feature = "ini"), | ||
| not(feature = "ron"), | ||
| not(feature = "json5"), | ||
| not(feature = "dotenv"), | ||
| ))] | ||
| _ => unreachable!("No features are enabled, this library won't work without features"), | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| #![cfg(feature = "dotenv")] | ||
|
|
||
| use config::Config; | ||
| use std::env; | ||
|
|
||
| #[test] | ||
| fn basic_dotenv() { | ||
| let s = Config::builder() | ||
| .add_source(config::File::from_str( | ||
| r#" | ||
| FOO=bar | ||
| BAZ=qux | ||
| "#, | ||
| config::FileFormat::Dotenv, | ||
| )) | ||
| .build() | ||
| .unwrap(); | ||
|
|
||
| assert_eq!(s.get::<String>("FOO").unwrap(), "bar"); | ||
| assert_eq!(s.get::<String>("BAZ").unwrap(), "qux"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn optional_variables() { | ||
| let s = Config::builder() | ||
| .add_source(config::File::from_str( | ||
| r#" | ||
| FOO=bar | ||
| BAZ=${FOO} | ||
| BAR=${UNDEFINED:-} | ||
| "#, | ||
| config::FileFormat::Dotenv, | ||
| )) | ||
| .build() | ||
| .unwrap(); | ||
|
|
||
| assert_eq!(s.get::<String>("BAR").unwrap(), ""); | ||
| } | ||
|
|
||
| #[test] | ||
| fn multiple_files() { | ||
| let s = Config::builder() | ||
| .add_source(config::File::from_str( | ||
| r#" | ||
| FOO=bar | ||
| "#, | ||
| config::FileFormat::Dotenv, | ||
| )) | ||
| .add_source(config::File::from_str( | ||
| r#" | ||
| BAZ=qux | ||
| "#, | ||
| config::FileFormat::Dotenv, | ||
| )) | ||
| .build() | ||
| .unwrap(); | ||
|
|
||
| assert_eq!(s.get::<String>("FOO").unwrap(), "bar"); | ||
| assert_eq!(s.get::<String>("BAZ").unwrap(), "qux"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn environment_overrides() { | ||
| env::set_var("FOOBAR", "env_value"); | ||
|
|
||
| let s = Config::builder() | ||
| .add_source(config::File::from_str( | ||
| r#" | ||
| FOOBAR=file_value | ||
| "#, | ||
| config::FileFormat::Dotenv, | ||
| )) | ||
| .add_source(config::Environment::with_prefix("env").separator("_")) | ||
| .build() | ||
| .unwrap(); | ||
|
|
||
| assert_eq!(s.get::<String>("FOOBAR").unwrap(), "env_value"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be doing manual layering, that is what the
ConfigBuilderis for.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dotenvy overrides OS var which is inconsistent with existing
FileFormatin this crate. Manual layering is appealing here because file formats sit belowConfigBuilderin the hierarchy. Coupling aConfigBuilderinside a file format will lead to difficult refactoring in the future.I think for the purposes of this specific file format, simple env override should suffice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Environmentand eachFileare manually added as sources and the user has control over the precedence order. However we support DotEnv, it will be similar, so we don't need to do this here. If we did,. it would belong inEnvironmentwhich is what is handling the OS vars.