diff --git a/Cargo.lock b/Cargo.lock index c26b58c49..1292741a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2134,6 +2134,7 @@ dependencies = [ "static-files", "sysinfo", "tar", + "temp-env", "tempfile", "thiserror", "time", @@ -3490,6 +3491,15 @@ dependencies = [ "xattr", ] +[[package]] +name = "temp-env" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a30d48359f77fbb6af3d7b928cc2d092e1dc90b44f397e979ef08ae15733ed65" +dependencies = [ + "once_cell", +] + [[package]] name = "tempfile" version = "3.3.0" diff --git a/meilisearch-http/Cargo.toml b/meilisearch-http/Cargo.toml index f57491cce..56dd3d745 100644 --- a/meilisearch-http/Cargo.toml +++ b/meilisearch-http/Cargo.toml @@ -91,6 +91,7 @@ manifest-dir-macros = "0.1.16" maplit = "1.0.2" urlencoding = "2.1.2" yaup = "0.2.1" +temp-env = "0.3.1" [features] default = ["analytics", "meilisearch-lib/default", "mini-dashboard"] diff --git a/meilisearch-http/src/option.rs b/meilisearch-http/src/option.rs index 586626836..e4ad7b8f2 100644 --- a/meilisearch-http/src/option.rs +++ b/meilisearch-http/src/option.rs @@ -1,3 +1,4 @@ +use std::env; use std::fs; use std::io::{BufReader, Read}; use std::path::PathBuf; @@ -51,6 +52,7 @@ const MEILI_LOG_LEVEL: &str = "MEILI_LOG_LEVEL"; #[cfg(feature = "metrics")] const MEILI_ENABLE_METRICS_ROUTE: &str = "MEILI_ENABLE_METRICS_ROUTE"; +const DEFAULT_CONFIG_FILE_PATH: &str = "./config.toml"; const DEFAULT_DB_PATH: &str = "./data.ms"; const DEFAULT_HTTP_ADDR: &str = "localhost:7700"; const DEFAULT_ENV: &str = "development"; @@ -261,32 +263,38 @@ impl Opt { // Parse the args to get the config_file_path. let mut opts = Opt::parse(); let mut config_read_from = None; - if let Some(config_file_path) = opts + let user_specified_config_file_path = opts .config_file_path .clone() - .or_else(|| Some(PathBuf::from("./config.toml"))) - { - match std::fs::read(&config_file_path) { - Ok(config) => { - // If the file is successfully read, we deserialize it with `toml`. - let opt_from_config = toml::from_slice::(&config)?; - // Return an error if config file contains 'config_file_path' - // Using that key in the config file doesn't make sense bc it creates a logical loop (config file referencing itself) - if opt_from_config.config_file_path.is_some() { - anyhow::bail!("`config_file_path` is not supported in config file") - } - // We inject the values from the toml in the corresponding env vars if needs be. Doing so, we respect the priority toml < env vars < cli args. - opt_from_config.export_to_env(); - // Once injected we parse the cli args once again to take the new env vars into scope. - opts = Opt::parse(); - config_read_from = Some(config_file_path); + .or_else(|| env::var("MEILI_CONFIG_FILE_PATH").map(PathBuf::from).ok()); + let config_file_path = user_specified_config_file_path + .clone() + .unwrap_or_else(|| PathBuf::from(DEFAULT_CONFIG_FILE_PATH)); + + match std::fs::read(&config_file_path) { + Ok(config) => { + // If the file is successfully read, we deserialize it with `toml`. + let opt_from_config = toml::from_slice::(&config)?; + // Return an error if config file contains 'config_file_path' + // Using that key in the config file doesn't make sense bc it creates a logical loop (config file referencing itself) + if opt_from_config.config_file_path.is_some() { + anyhow::bail!("`config_file_path` is not supported in config file") + } + // We inject the values from the toml in the corresponding env vars if needs be. Doing so, we respect the priority toml < env vars < cli args. + opt_from_config.export_to_env(); + // Once injected we parse the cli args once again to take the new env vars into scope. + opts = Opt::parse(); + config_read_from = Some(config_file_path); + } + Err(e) => { + if let Some(path) = user_specified_config_file_path { + // If we have an error while reading the file defined by the user. + anyhow::bail!( + "unable to open or read the {:?} configuration file: {}.", + path, + e, + ) } - // If we have an error while reading the file defined by the user. - Err(_) if opts.config_file_path.is_some() => anyhow::bail!( - "unable to open or read the {:?} configuration file.", - opts.config_file_path.unwrap().display().to_string() - ), - _ => (), } } @@ -521,10 +529,41 @@ fn default_log_level() -> String { #[cfg(test)] mod test { + use super::*; #[test] fn test_valid_opt() { assert!(Opt::try_parse_from(Some("")).is_ok()); } + + #[test] + fn test_meilli_config_file_path_valid() { + temp_env::with_vars( + vec![("MEILI_CONFIG_FILE_PATH", Some("../config.toml"))], // Relative path in meilisearch_http package + || { + assert!(Opt::try_build().is_ok()); + }, + ); + } + + #[test] + fn test_meilli_config_file_path_invalid() { + temp_env::with_vars( + vec![("MEILI_CONFIG_FILE_PATH", Some("../configgg.toml"))], + || { + let possible_error_messages = [ + "unable to open or read the \"../configgg.toml\" configuration file: No such file or directory (os error 2).", + "unable to open or read the \"../configgg.toml\" configuration file: The system cannot find the file specified. (os error 2).", // Windows + ]; + let error_message = Opt::try_build().unwrap_err().to_string(); + assert!( + possible_error_messages.contains(&error_message.as_str()), + "Expected onf of {:?}, got {:?}.", + possible_error_messages, + error_message + ); + }, + ); + } }