2022-09-27 16:33:37 +02:00
use std ::convert ::TryFrom ;
2022-10-20 18:00:07 +02:00
use std ::env ::VarError ;
use std ::ffi ::OsStr ;
2023-01-03 11:55:34 +01:00
use std ::fmt ::Display ;
2020-12-12 13:32:06 +01:00
use std ::io ::{ BufReader , Read } ;
2022-09-27 16:33:37 +02:00
use std ::num ::ParseIntError ;
use std ::ops ::Deref ;
2020-12-12 13:32:06 +01:00
use std ::path ::PathBuf ;
2022-09-27 16:33:37 +02:00
use std ::str ::FromStr ;
2020-12-12 13:32:06 +01:00
use std ::sync ::Arc ;
2022-10-11 13:24:53 +02:00
use std ::{ env , fmt , fs } ;
2020-12-12 13:32:06 +01:00
2022-09-27 16:33:37 +02:00
use byte_unit ::{ Byte , ByteError } ;
2022-01-12 13:54:39 +01:00
use clap ::Parser ;
2022-10-11 17:42:43 +02:00
use meilisearch_types ::milli ::update ::IndexerConfig ;
2022-10-20 18:00:07 +02:00
use rustls ::server ::{
AllowAnyAnonymousOrAuthenticatedClient , AllowAnyAuthenticatedClient , ServerSessionMemoryCache ,
2020-12-12 13:32:06 +01:00
} ;
2022-10-20 18:00:07 +02:00
use rustls ::RootCertStore ;
2022-01-21 20:44:17 +00:00
use rustls_pemfile ::{ certs , pkcs8_private_keys , rsa_private_keys } ;
2022-09-06 09:23:16 +02:00
use serde ::{ Deserialize , Serialize } ;
2022-09-27 16:33:37 +02:00
use sysinfo ::{ RefreshKind , System , SystemExt } ;
2021-01-29 19:14:23 +01:00
2020-12-12 13:32:06 +01:00
const POSSIBLE_ENV : [ & str ; 2 ] = [ " development " , " production " ] ;
2022-09-06 09:23:16 +02:00
const MEILI_DB_PATH : & str = " MEILI_DB_PATH " ;
const MEILI_HTTP_ADDR : & str = " MEILI_HTTP_ADDR " ;
const MEILI_MASTER_KEY : & str = " MEILI_MASTER_KEY " ;
const MEILI_ENV : & str = " MEILI_ENV " ;
#[ cfg(all(not(debug_assertions), feature = " analytics " )) ]
const MEILI_NO_ANALYTICS : & str = " MEILI_NO_ANALYTICS " ;
const MEILI_HTTP_PAYLOAD_SIZE_LIMIT : & str = " MEILI_HTTP_PAYLOAD_SIZE_LIMIT " ;
const MEILI_SSL_CERT_PATH : & str = " MEILI_SSL_CERT_PATH " ;
const MEILI_SSL_KEY_PATH : & str = " MEILI_SSL_KEY_PATH " ;
const MEILI_SSL_AUTH_PATH : & str = " MEILI_SSL_AUTH_PATH " ;
const MEILI_SSL_OCSP_PATH : & str = " MEILI_SSL_OCSP_PATH " ;
const MEILI_SSL_REQUIRE_AUTH : & str = " MEILI_SSL_REQUIRE_AUTH " ;
const MEILI_SSL_RESUMPTION : & str = " MEILI_SSL_RESUMPTION " ;
const MEILI_SSL_TICKETS : & str = " MEILI_SSL_TICKETS " ;
2022-09-07 17:47:15 +02:00
const MEILI_IMPORT_SNAPSHOT : & str = " MEILI_IMPORT_SNAPSHOT " ;
const MEILI_IGNORE_MISSING_SNAPSHOT : & str = " MEILI_IGNORE_MISSING_SNAPSHOT " ;
const MEILI_IGNORE_SNAPSHOT_IF_DB_EXISTS : & str = " MEILI_IGNORE_SNAPSHOT_IF_DB_EXISTS " ;
2022-09-06 09:23:16 +02:00
const MEILI_SNAPSHOT_DIR : & str = " MEILI_SNAPSHOT_DIR " ;
const MEILI_SCHEDULE_SNAPSHOT : & str = " MEILI_SCHEDULE_SNAPSHOT " ;
2022-09-07 17:47:15 +02:00
const MEILI_IMPORT_DUMP : & str = " MEILI_IMPORT_DUMP " ;
const MEILI_IGNORE_MISSING_DUMP : & str = " MEILI_IGNORE_MISSING_DUMP " ;
const MEILI_IGNORE_DUMP_IF_DB_EXISTS : & str = " MEILI_IGNORE_DUMP_IF_DB_EXISTS " ;
2022-12-01 15:39:36 +01:00
const MEILI_DUMP_DIR : & str = " MEILI_DUMP_DIR " ;
2022-09-06 09:23:16 +02:00
const MEILI_LOG_LEVEL : & str = " MEILI_LOG_LEVEL " ;
2023-02-21 18:27:45 +01:00
const MEILI_EXPERIMENTAL_ENABLE_METRICS : & str = " MEILI_EXPERIMENTAL_ENABLE_METRICS " ;
2022-09-06 09:23:16 +02:00
2022-10-11 21:35:07 +08:00
const DEFAULT_CONFIG_FILE_PATH : & str = " ./config.toml " ;
2022-09-07 11:51:23 +02:00
const DEFAULT_DB_PATH : & str = " ./data.ms " ;
2022-10-05 20:14:37 -04:00
const DEFAULT_HTTP_ADDR : & str = " localhost:7700 " ;
2022-09-07 11:51:23 +02:00
const DEFAULT_ENV : & str = " development " ;
const DEFAULT_HTTP_PAYLOAD_SIZE_LIMIT : & str = " 100 MB " ;
const DEFAULT_SNAPSHOT_DIR : & str = " snapshots/ " ;
const DEFAULT_SNAPSHOT_INTERVAL_SEC : u64 = 86400 ;
2022-12-27 18:17:29 +01:00
const DEFAULT_SNAPSHOT_INTERVAL_SEC_STR : & str = " 86400 " ;
2022-12-01 15:39:36 +01:00
const DEFAULT_DUMP_DIR : & str = " dumps/ " ;
2022-09-07 11:51:23 +02:00
2022-10-20 16:49:19 +02:00
const MEILI_MAX_INDEXING_MEMORY : & str = " MEILI_MAX_INDEXING_MEMORY " ;
const MEILI_MAX_INDEXING_THREADS : & str = " MEILI_MAX_INDEXING_THREADS " ;
2023-01-05 14:25:41 +01:00
const DEFAULT_LOG_EVERY_N : usize = 100_000 ;
2022-10-20 16:49:19 +02:00
2023-01-04 16:53:45 +01:00
// Each environment (index and task-db) is taking space in the virtual address space.
2023-02-22 09:04:52 +01:00
// Ideally, indexes can occupy 2TiB each to avoid having to manually resize them.
// The actual size of the virtual address space is computed at startup to determine how many 2TiB indexes can be
// opened simultaneously.
pub const INDEX_SIZE : u64 = 2 * 1024 * 1024 * 1024 * 1024 ; // 2 TiB
2023-01-11 17:34:46 +01:00
pub const TASK_DB_SIZE : u64 = 10 * 1024 * 1024 * 1024 ; // 10 GiB
2022-12-26 11:41:31 +01:00
2023-01-03 11:55:34 +01:00
#[ derive(Debug, Default, Clone, Copy, Serialize, Deserialize) ]
#[ serde(rename_all = " UPPERCASE " ) ]
pub enum LogLevel {
Off ,
Error ,
Warn ,
#[ default ]
Info ,
Debug ,
Trace ,
}
#[ derive(Debug) ]
pub struct LogLevelError {
pub given_log_level : String ,
}
2023-01-05 14:25:41 +01:00
2023-01-03 11:55:34 +01:00
impl Display for LogLevelError {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
writeln! (
f ,
" Log level '{}' is invalid. Accepted values are 'OFF', 'ERROR', 'WARN', 'INFO', 'DEBUG', and 'TRACE'. " ,
self . given_log_level
)
}
}
2023-01-05 14:25:41 +01:00
2023-01-03 11:55:34 +01:00
impl Display for LogLevel {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
match self {
LogLevel ::Off = > Display ::fmt ( " OFF " , f ) ,
LogLevel ::Error = > Display ::fmt ( " ERROR " , f ) ,
LogLevel ::Warn = > Display ::fmt ( " WARN " , f ) ,
LogLevel ::Info = > Display ::fmt ( " INFO " , f ) ,
LogLevel ::Debug = > Display ::fmt ( " DEBUG " , f ) ,
LogLevel ::Trace = > Display ::fmt ( " TRACE " , f ) ,
}
}
}
2023-01-05 14:25:41 +01:00
2023-01-03 11:55:34 +01:00
impl std ::error ::Error for LogLevelError { }
2023-01-05 14:25:41 +01:00
2023-01-03 11:55:34 +01:00
impl FromStr for LogLevel {
type Err = LogLevelError ;
fn from_str ( s : & str ) -> Result < Self , Self ::Err > {
match s . trim ( ) . to_lowercase ( ) . as_str ( ) {
" off " = > Ok ( LogLevel ::Off ) ,
" error " = > Ok ( LogLevel ::Error ) ,
" warn " = > Ok ( LogLevel ::Warn ) ,
" info " = > Ok ( LogLevel ::Info ) ,
" debug " = > Ok ( LogLevel ::Debug ) ,
" trace " = > Ok ( LogLevel ::Trace ) ,
_ = > Err ( LogLevelError { given_log_level : s . to_owned ( ) } ) ,
}
}
}
2022-11-28 16:27:41 +01:00
#[ derive(Debug, Clone, Parser, Deserialize) ]
2022-10-14 16:43:01 +08:00
#[ clap(version, next_display_order = None) ]
2022-09-19 18:16:28 +02:00
#[ serde(rename_all = " snake_case " , deny_unknown_fields) ]
2020-12-12 13:32:06 +01:00
pub struct Opt {
2022-10-03 16:07:38 +02:00
/// Designates the location where database files will be created and retrieved.
2022-09-07 11:51:23 +02:00
#[ clap(long, env = MEILI_DB_PATH, default_value_os_t = default_db_path()) ]
#[ serde(default = " default_db_path " ) ]
2020-12-22 17:13:50 +01:00
pub db_path : PathBuf ,
2020-12-12 13:32:06 +01:00
2022-10-05 19:04:33 +02:00
/// Sets the HTTP address and port Meilisearch will use.
2022-09-07 11:51:23 +02:00
#[ clap(long, env = MEILI_HTTP_ADDR, default_value_t = default_http_addr()) ]
#[ serde(default = " default_http_addr " ) ]
2020-12-12 13:32:06 +01:00
pub http_addr : String ,
2022-10-05 19:04:33 +02:00
/// Sets the instance's master key, automatically protecting all routes except `GET /health`.
2022-09-06 09:23:16 +02:00
#[ clap(long, env = MEILI_MASTER_KEY) ]
2020-12-12 13:32:06 +01:00
pub master_key : Option < String > ,
2022-10-05 18:54:38 +02:00
/// Configures the instance's environment. Value must be either `production` or `development`.
2022-10-04 20:59:57 +08:00
#[ clap(long, env = MEILI_ENV, default_value_t = default_env(), value_parser = POSSIBLE_ENV) ]
2022-09-07 11:51:23 +02:00
#[ serde(default = " default_env " ) ]
2020-12-12 13:32:06 +01:00
pub env : String ,
2022-10-03 16:07:38 +02:00
/// Deactivates Meilisearch's built-in telemetry when provided.
///
/// Meilisearch automatically collects data from all instances that do not opt out using this flag.
/// All gathered data is used solely for the purpose of improving Meilisearch, and can be deleted
/// at any time.
2021-06-16 17:12:49 +02:00
#[ cfg(all(not(debug_assertions), feature = " analytics " )) ]
2022-11-28 16:27:41 +01:00
#[ serde(default) ] // we can't send true
2022-09-06 09:23:16 +02:00
#[ clap(long, env = MEILI_NO_ANALYTICS) ]
2022-01-12 13:54:39 +01:00
pub no_analytics : bool ,
2020-12-12 13:32:06 +01:00
2022-10-05 18:54:38 +02:00
/// Sets the maximum size of the index. Value must be given in bytes or explicitly stating a base unit (for instance: 107374182400, '107.7Gb', or '107374 Mb').
2022-12-26 11:41:31 +01:00
#[ clap(skip = default_max_index_size()) ]
#[ serde(skip, default = " default_max_index_size " ) ]
2021-06-16 19:50:15 +02:00
pub max_index_size : Byte ,
2020-12-12 13:32:06 +01:00
2022-10-03 16:07:38 +02:00
/// Sets the maximum size of the task database. Value must be given in bytes or explicitly stating a
2022-10-05 18:54:38 +02:00
/// base unit (for instance: 107374182400, '107.7Gb', or '107374 Mb').
2022-12-26 11:41:31 +01:00
#[ clap(skip = default_max_task_db_size()) ]
#[ serde(skip, default = " default_max_task_db_size " ) ]
2021-12-02 16:03:26 +01:00
pub max_task_db_size : Byte ,
2020-12-12 13:32:06 +01:00
2022-10-03 16:07:38 +02:00
/// Sets the maximum size of accepted payloads. Value must be given in bytes or explicitly stating a
2022-10-05 18:54:38 +02:00
/// base unit (for instance: 107374182400, '107.7Gb', or '107374 Mb').
2022-09-07 11:51:23 +02:00
#[ clap(long, env = MEILI_HTTP_PAYLOAD_SIZE_LIMIT, default_value_t = default_http_payload_size_limit()) ]
#[ serde(default = " default_http_payload_size_limit " ) ]
2020-12-22 14:02:41 +01:00
pub http_payload_size_limit : Byte ,
2020-12-12 13:32:06 +01:00
2022-10-03 16:07:38 +02:00
/// Sets the server's SSL certificates.
2022-10-04 20:59:57 +08:00
#[ clap(long, env = MEILI_SSL_CERT_PATH, value_parser) ]
2020-12-12 13:32:06 +01:00
pub ssl_cert_path : Option < PathBuf > ,
2022-10-03 16:07:38 +02:00
/// Sets the server's SSL key files.
2022-10-04 20:59:57 +08:00
#[ clap(long, env = MEILI_SSL_KEY_PATH, value_parser) ]
2020-12-12 13:32:06 +01:00
pub ssl_key_path : Option < PathBuf > ,
2022-10-03 16:07:38 +02:00
/// Enables client authentication in the specified path.
2022-10-04 20:59:57 +08:00
#[ clap(long, env = MEILI_SSL_AUTH_PATH, value_parser) ]
2020-12-12 13:32:06 +01:00
pub ssl_auth_path : Option < PathBuf > ,
2022-10-05 19:04:33 +02:00
/// Sets the server's OCSP file. *Optional*
///
/// Reads DER-encoded OCSP response from OCSPFILE and staple to certificate.
2022-10-04 20:59:57 +08:00
#[ clap(long, env = MEILI_SSL_OCSP_PATH, value_parser) ]
2020-12-12 13:32:06 +01:00
pub ssl_ocsp_path : Option < PathBuf > ,
2022-10-03 16:07:38 +02:00
/// Makes SSL authentication mandatory.
2022-11-28 16:27:41 +01:00
#[ serde(default) ]
2022-09-06 09:23:16 +02:00
#[ clap(long, env = MEILI_SSL_REQUIRE_AUTH) ]
2020-12-12 13:32:06 +01:00
pub ssl_require_auth : bool ,
2022-10-03 16:07:38 +02:00
/// Activates SSL session resumption.
2022-11-28 16:27:41 +01:00
#[ serde(default) ]
2022-09-06 09:23:16 +02:00
#[ clap(long, env = MEILI_SSL_RESUMPTION) ]
2020-12-12 13:32:06 +01:00
pub ssl_resumption : bool ,
2022-10-03 16:07:38 +02:00
/// Activates SSL tickets.
2022-11-28 16:27:41 +01:00
#[ serde(default) ]
2022-09-06 09:23:16 +02:00
#[ clap(long, env = MEILI_SSL_TICKETS) ]
2020-12-12 13:32:06 +01:00
pub ssl_tickets : bool ,
2022-10-03 16:07:38 +02:00
/// Launches Meilisearch after importing a previously-generated snapshot at the given filepath.
2022-09-07 17:47:15 +02:00
#[ clap(long, env = MEILI_IMPORT_SNAPSHOT) ]
2020-12-12 13:32:06 +01:00
pub import_snapshot : Option < PathBuf > ,
2022-10-03 16:07:38 +02:00
/// Prevents a Meilisearch instance from throwing an error when `--import-snapshot`
/// does not point to a valid snapshot file.
///
/// This command will throw an error if `--import-snapshot` is not defined.
2022-09-01 22:37:07 +02:00
#[ clap(
long ,
2022-09-07 17:47:15 +02:00
env = MEILI_IGNORE_MISSING_SNAPSHOT ,
2022-10-07 00:32:08 +08:00
requires = " import_snapshot "
2022-09-01 22:37:07 +02:00
) ]
2022-09-07 11:51:23 +02:00
#[ serde(default) ]
2020-12-12 13:32:06 +01:00
pub ignore_missing_snapshot : bool ,
2022-10-03 16:07:38 +02:00
/// Prevents a Meilisearch instance with an existing database from throwing an
/// error when using `--import-snapshot`. Instead, the snapshot will be ignored
/// and Meilisearch will launch using the existing database.
///
/// This command will throw an error if `--import-snapshot` is not defined.
2022-09-01 22:37:07 +02:00
#[ clap(
long ,
2022-09-07 17:47:15 +02:00
env = MEILI_IGNORE_SNAPSHOT_IF_DB_EXISTS ,
2022-10-07 00:32:08 +08:00
requires = " import_snapshot "
2022-09-01 22:37:07 +02:00
) ]
2022-09-07 11:51:23 +02:00
#[ serde(default) ]
2020-12-12 13:32:06 +01:00
pub ignore_snapshot_if_db_exists : bool ,
2022-10-03 16:07:38 +02:00
/// Sets the directory where Meilisearch will store snapshots.
2022-09-07 11:51:23 +02:00
#[ clap(long, env = MEILI_SNAPSHOT_DIR, default_value_os_t = default_snapshot_dir()) ]
#[ serde(default = " default_snapshot_dir " ) ]
2020-12-12 13:32:06 +01:00
pub snapshot_dir : PathBuf ,
2022-10-03 16:07:38 +02:00
/// Activates scheduled snapshots when provided. Snapshots are disabled by default.
2022-12-27 18:17:29 +01:00
///
/// When provided with a value, defines the interval between each snapshot, in seconds.
#[ clap(long,env = MEILI_SCHEDULE_SNAPSHOT, num_args(0..=1), value_parser=parse_schedule_snapshot, default_value_t, default_missing_value=default_snapshot_interval_sec(), value_name = " SNAPSHOT_INTERVAL_SEC " ) ]
#[ serde(default, deserialize_with = " schedule_snapshot_deserialize " ) ]
pub schedule_snapshot : ScheduleSnapshot ,
2020-12-12 13:32:06 +01:00
2022-10-03 16:37:16 +02:00
/// Imports the dump file located at the specified path. Path must point to a `.dump` file.
/// If a database already exists, Meilisearch will throw an error and abort launch.
2022-10-07 00:32:08 +08:00
#[ clap(long, env = MEILI_IMPORT_DUMP, conflicts_with = " import_snapshot " ) ]
2020-12-12 13:32:06 +01:00
pub import_dump : Option < PathBuf > ,
2022-10-03 16:37:16 +02:00
/// Prevents Meilisearch from throwing an error when `--import-dump` does not point to
/// a valid dump file. Instead, Meilisearch will start normally without importing any dump.
///
/// This option will trigger an error if `--import-dump` is not defined.
2022-10-07 00:32:08 +08:00
#[ clap(long, env = MEILI_IGNORE_MISSING_DUMP, requires = " import_dump " ) ]
2022-09-07 11:51:23 +02:00
#[ serde(default) ]
2022-01-20 16:00:14 +01:00
pub ignore_missing_dump : bool ,
2022-10-03 16:37:16 +02:00
/// Prevents a Meilisearch instance with an existing database from throwing an error
/// when using `--import-dump`. Instead, the dump will be ignored and Meilisearch will
/// launch using the existing database.
///
/// This option will trigger an error if `--import-dump` is not defined.
2022-10-07 00:32:08 +08:00
#[ clap(long, env = MEILI_IGNORE_DUMP_IF_DB_EXISTS, requires = " import_dump " ) ]
2022-09-07 11:51:23 +02:00
#[ serde(default) ]
2022-01-20 16:00:14 +01:00
pub ignore_dump_if_db_exists : bool ,
2022-10-05 19:04:33 +02:00
/// Sets the directory where Meilisearch will create dump files.
2022-12-01 15:39:36 +01:00
#[ clap(long, env = MEILI_DUMP_DIR, default_value_os_t = default_dump_dir()) ]
#[ serde(default = " default_dump_dir " ) ]
pub dump_dir : PathBuf ,
2022-01-20 16:00:14 +01:00
2022-10-03 16:37:16 +02:00
/// Defines how much detail should be present in Meilisearch's logs.
///
2023-01-03 11:55:34 +01:00
/// Meilisearch currently supports six log levels, listed in order of increasing verbosity: OFF, ERROR, WARN, INFO, DEBUG, TRACE.
#[ clap(long, env = MEILI_LOG_LEVEL, default_value_t) ]
#[ serde(default) ]
pub log_level : LogLevel ,
2021-06-23 11:07:19 +02:00
2023-02-23 12:21:18 +01:00
/// Experimental metrics feature. For more information, see: <https://github.com/meilisearch/meilisearch/discussions/3518>
2023-02-21 18:27:45 +01:00
///
/// Enables the Prometheus metrics on the `GET /metrics` endpoint.
#[ clap(long, env = MEILI_EXPERIMENTAL_ENABLE_METRICS) ]
2022-09-07 11:51:23 +02:00
#[ serde(default) ]
2023-02-21 18:27:45 +01:00
pub experimental_enable_metrics : bool ,
2022-08-17 17:14:55 +05:30
2022-03-24 18:52:36 +00:00
#[ serde(flatten) ]
#[ clap(flatten) ]
2020-12-22 17:13:50 +01:00
pub indexer_options : IndexerOpts ,
2022-01-19 11:21:19 +01:00
2022-10-05 19:04:33 +02:00
/// Set the path to a configuration file that should be used to setup the engine.
2022-09-06 14:50:49 +02:00
/// Format must be TOML.
#[ clap(long) ]
2022-09-07 20:22:49 +02:00
pub config_file_path : Option < PathBuf > ,
2020-12-12 13:32:06 +01:00
}
impl Opt {
2022-09-20 22:39:35 +08:00
/// Whether analytics should be enabled or not.
2022-01-19 11:21:19 +01:00
#[ cfg(all(not(debug_assertions), feature = " analytics " )) ]
pub fn analytics ( & self ) -> bool {
! self . no_analytics
}
2022-08-22 10:30:07 +05:30
2022-09-07 18:16:33 +02:00
/// Build a new Opt from config file, env vars and cli args.
2022-09-19 18:16:28 +02:00
pub fn try_build ( ) -> anyhow ::Result < ( Self , Option < PathBuf > ) > {
2022-09-07 18:16:33 +02:00
// Parse the args to get the config_file_path.
2022-09-06 14:50:49 +02:00
let mut opts = Opt ::parse ( ) ;
2022-09-19 18:16:28 +02:00
let mut config_read_from = None ;
2022-10-12 11:58:28 +08:00
let user_specified_config_file_path = opts
2022-09-19 18:16:28 +02:00
. config_file_path
. clone ( )
2022-10-12 11:58:28 +08:00
. or_else ( | | env ::var ( " MEILI_CONFIG_FILE_PATH " ) . map ( PathBuf ::from ) . ok ( ) ) ;
let config_file_path = user_specified_config_file_path
. clone ( )
2022-09-27 22:58:25 +08:00
. 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 ::< Opt > ( & 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 ( ) {
2022-10-25 16:16:34 +05:30
anyhow ::bail! ( " `config_file_path` is not supported in the configuration file " )
2022-09-06 14:50:49 +02:00
}
2022-09-27 22:58:25 +08:00
// 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 ) ;
2022-09-06 09:23:16 +02:00
}
2022-10-12 11:58:28 +08:00
Err ( e ) = > {
2022-10-14 14:16:10 +08:00
if let Some ( path ) = user_specified_config_file_path {
2022-10-12 11:58:28 +08:00
// If we have an error while reading the file defined by the user.
2022-10-14 14:16:10 +08:00
anyhow ::bail! (
2022-10-12 11:58:28 +08:00
" unable to open or read the {:?} configuration file: {}. " ,
path ,
e ,
2022-10-14 14:16:10 +08:00
)
2022-09-06 14:50:49 +02:00
}
2022-09-06 09:23:16 +02:00
}
}
2022-09-19 18:16:28 +02:00
Ok ( ( opts , config_read_from ) )
2022-09-06 09:23:16 +02:00
}
2022-09-07 18:16:33 +02:00
/// Exports the opts values to their corresponding env vars if they are not set.
2022-09-06 09:23:16 +02:00
fn export_to_env ( self ) {
2022-09-19 18:16:28 +02:00
let Opt {
db_path ,
http_addr ,
master_key ,
env ,
2022-12-26 11:41:31 +01:00
max_index_size : _ ,
max_task_db_size : _ ,
2022-09-19 18:16:28 +02:00
http_payload_size_limit ,
ssl_cert_path ,
ssl_key_path ,
ssl_auth_path ,
ssl_ocsp_path ,
ssl_require_auth ,
ssl_resumption ,
ssl_tickets ,
snapshot_dir ,
schedule_snapshot ,
2022-12-01 15:39:36 +01:00
dump_dir ,
2022-09-19 18:16:28 +02:00
log_level ,
indexer_options ,
import_snapshot : _ ,
ignore_missing_snapshot : _ ,
ignore_snapshot_if_db_exists : _ ,
import_dump : _ ,
ignore_missing_dump : _ ,
ignore_dump_if_db_exists : _ ,
config_file_path : _ ,
#[ cfg(all(not(debug_assertions), feature = " analytics " )) ]
no_analytics ,
2023-02-21 18:27:45 +01:00
experimental_enable_metrics : enable_metrics_route ,
2022-09-19 18:16:28 +02:00
} = self ;
export_to_env_if_not_present ( MEILI_DB_PATH , db_path ) ;
export_to_env_if_not_present ( MEILI_HTTP_ADDR , http_addr ) ;
if let Some ( master_key ) = master_key {
2022-09-06 09:23:16 +02:00
export_to_env_if_not_present ( MEILI_MASTER_KEY , master_key ) ;
}
2022-09-19 18:16:28 +02:00
export_to_env_if_not_present ( MEILI_ENV , env ) ;
2022-09-06 09:23:16 +02:00
#[ cfg(all(not(debug_assertions), feature = " analytics " )) ]
{
2022-09-19 18:16:28 +02:00
export_to_env_if_not_present ( MEILI_NO_ANALYTICS , no_analytics . to_string ( ) ) ;
2022-09-06 09:23:16 +02:00
}
export_to_env_if_not_present (
MEILI_HTTP_PAYLOAD_SIZE_LIMIT ,
2022-09-19 18:16:28 +02:00
http_payload_size_limit . to_string ( ) ,
2022-09-06 09:23:16 +02:00
) ;
2022-09-19 18:16:28 +02:00
if let Some ( ssl_cert_path ) = ssl_cert_path {
2022-09-06 09:23:16 +02:00
export_to_env_if_not_present ( MEILI_SSL_CERT_PATH , ssl_cert_path ) ;
}
2022-09-19 18:16:28 +02:00
if let Some ( ssl_key_path ) = ssl_key_path {
2022-09-06 09:23:16 +02:00
export_to_env_if_not_present ( MEILI_SSL_KEY_PATH , ssl_key_path ) ;
}
2022-09-19 18:16:28 +02:00
if let Some ( ssl_auth_path ) = ssl_auth_path {
2022-09-06 09:23:16 +02:00
export_to_env_if_not_present ( MEILI_SSL_AUTH_PATH , ssl_auth_path ) ;
}
2022-09-19 18:16:28 +02:00
if let Some ( ssl_ocsp_path ) = ssl_ocsp_path {
2022-09-06 09:23:16 +02:00
export_to_env_if_not_present ( MEILI_SSL_OCSP_PATH , ssl_ocsp_path ) ;
}
2022-09-19 18:16:28 +02:00
export_to_env_if_not_present ( MEILI_SSL_REQUIRE_AUTH , ssl_require_auth . to_string ( ) ) ;
export_to_env_if_not_present ( MEILI_SSL_RESUMPTION , ssl_resumption . to_string ( ) ) ;
export_to_env_if_not_present ( MEILI_SSL_TICKETS , ssl_tickets . to_string ( ) ) ;
export_to_env_if_not_present ( MEILI_SNAPSHOT_DIR , snapshot_dir ) ;
2022-12-27 18:17:29 +01:00
if let Some ( snapshot_interval ) = schedule_snapshot_to_env ( schedule_snapshot ) {
export_to_env_if_not_present ( MEILI_SCHEDULE_SNAPSHOT , snapshot_interval )
}
2022-12-01 15:39:36 +01:00
export_to_env_if_not_present ( MEILI_DUMP_DIR , dump_dir ) ;
2023-01-03 11:55:34 +01:00
export_to_env_if_not_present ( MEILI_LOG_LEVEL , log_level . to_string ( ) ) ;
2023-02-21 18:27:45 +01:00
export_to_env_if_not_present (
MEILI_EXPERIMENTAL_ENABLE_METRICS ,
enable_metrics_route . to_string ( ) ,
) ;
2022-09-19 18:16:28 +02:00
indexer_options . export_to_env ( ) ;
2022-09-06 09:23:16 +02:00
}
2021-09-14 18:39:02 +02:00
pub fn get_ssl_config ( & self ) -> anyhow ::Result < Option < rustls ::ServerConfig > > {
2020-12-12 13:32:06 +01:00
if let ( Some ( cert_path ) , Some ( key_path ) ) = ( & self . ssl_cert_path , & self . ssl_key_path ) {
2022-01-21 20:44:17 +00:00
let config = rustls ::ServerConfig ::builder ( ) . with_safe_defaults ( ) ;
let config = match & self . ssl_auth_path {
2020-12-12 13:32:06 +01:00
Some ( auth_path ) = > {
let roots = load_certs ( auth_path . to_path_buf ( ) ) ? ;
let mut client_auth_roots = RootCertStore ::empty ( ) ;
for root in roots {
client_auth_roots . add ( & root ) . unwrap ( ) ;
}
if self . ssl_require_auth {
2022-01-21 20:44:17 +00:00
let verifier = AllowAnyAuthenticatedClient ::new ( client_auth_roots ) ;
config . with_client_cert_verifier ( verifier )
2020-12-12 13:32:06 +01:00
} else {
2022-01-21 20:44:17 +00:00
let verifier =
AllowAnyAnonymousOrAuthenticatedClient ::new ( client_auth_roots ) ;
config . with_client_cert_verifier ( verifier )
2020-12-12 13:32:06 +01:00
}
}
2022-01-21 20:44:17 +00:00
None = > config . with_no_client_auth ( ) ,
2020-12-12 13:32:06 +01:00
} ;
let certs = load_certs ( cert_path . to_path_buf ( ) ) ? ;
let privkey = load_private_key ( key_path . to_path_buf ( ) ) ? ;
let ocsp = load_ocsp ( & self . ssl_ocsp_path ) ? ;
2022-01-21 20:44:17 +00:00
let mut config = config
. with_single_cert_with_ocsp_and_sct ( certs , privkey , ocsp , vec! [ ] )
2021-09-14 18:39:02 +02:00
. map_err ( | _ | anyhow ::anyhow! ( " bad certificates/private key " ) ) ? ;
2020-12-12 13:32:06 +01:00
2022-01-21 20:44:17 +00:00
config . key_log = Arc ::new ( rustls ::KeyLogFile ::new ( ) ) ;
2020-12-12 13:32:06 +01:00
if self . ssl_resumption {
2022-01-21 20:44:17 +00:00
config . session_storage = ServerSessionMemoryCache ::new ( 256 ) ;
2020-12-12 13:32:06 +01:00
}
if self . ssl_tickets {
2022-01-21 20:44:17 +00:00
config . ticketer = rustls ::Ticketer ::new ( ) . unwrap ( ) ;
2020-12-12 13:32:06 +01:00
}
Ok ( Some ( config ) )
} else {
Ok ( None )
}
}
}
2023-01-05 16:00:45 +01:00
#[ derive(Debug, Default, Clone, Parser, Deserialize) ]
2022-09-27 16:33:37 +02:00
pub struct IndexerOpts {
2022-10-20 17:38:57 +02:00
/// Sets the maximum amount of RAM Meilisearch can use when indexing. By default, Meilisearch
/// uses no more than two thirds of available memory.
2022-10-20 16:49:19 +02:00
#[ clap(long, env = MEILI_MAX_INDEXING_MEMORY, default_value_t) ]
#[ serde(default) ]
2022-09-27 16:33:37 +02:00
pub max_indexing_memory : MaxMemory ,
2022-10-20 17:38:57 +02:00
/// Sets the maximum number of threads Meilisearch can use during indexation. By default, the
/// indexer avoids using more than half of a machine's total processing units. This ensures
/// Meilisearch is always ready to perform searches, even while you are updating an index.
2022-10-20 16:49:19 +02:00
#[ clap(long, env = MEILI_MAX_INDEXING_THREADS, default_value_t) ]
#[ serde(default) ]
2022-09-27 16:33:37 +02:00
pub max_indexing_threads : MaxThreads ,
2023-02-15 12:31:14 +01:00
/// Whether or not we want to determine the budget of virtual memory address space we have available dynamically
/// (the default), or statically.
///
/// Determining the budget of virtual memory address space dynamically takes some time on some systems (such as macOS)
/// and may make tests non-deterministic, so we want to skip it in tests.
#[ clap(skip) ]
#[ serde(skip) ]
pub skip_index_budget : bool ,
2022-09-27 16:33:37 +02:00
}
2022-10-20 16:49:19 +02:00
impl IndexerOpts {
/// Exports the values to their corresponding env vars if they are not set.
pub fn export_to_env ( self ) {
2023-02-15 12:31:14 +01:00
let IndexerOpts { max_indexing_memory , max_indexing_threads , skip_index_budget : _ } = self ;
2022-10-20 16:49:19 +02:00
if let Some ( max_indexing_memory ) = max_indexing_memory . 0 {
export_to_env_if_not_present (
MEILI_MAX_INDEXING_MEMORY ,
max_indexing_memory . to_string ( ) ,
) ;
}
export_to_env_if_not_present (
MEILI_MAX_INDEXING_THREADS ,
max_indexing_threads . 0. to_string ( ) ,
) ;
}
}
2022-09-27 16:33:37 +02:00
impl TryFrom < & IndexerOpts > for IndexerConfig {
type Error = anyhow ::Error ;
fn try_from ( other : & IndexerOpts ) -> Result < Self , Self ::Error > {
2022-11-28 16:27:41 +01:00
let thread_pool = rayon ::ThreadPoolBuilder ::new ( )
. thread_name ( | index | format! ( " indexing-thread: {index} " ) )
. num_threads ( * other . max_indexing_threads )
. build ( ) ? ;
2022-09-27 16:33:37 +02:00
Ok ( Self {
2023-01-05 14:25:41 +01:00
log_every_n : Some ( DEFAULT_LOG_EVERY_N ) ,
2022-09-27 16:33:37 +02:00
max_memory : other . max_indexing_memory . map ( | b | b . get_bytes ( ) as usize ) ,
thread_pool : Some ( thread_pool ) ,
max_positions_per_attributes : None ,
2023-02-15 12:31:14 +01:00
skip_index_budget : other . skip_index_budget ,
2022-09-27 16:33:37 +02:00
.. Default ::default ( )
} )
}
}
/// A type used to detect the max memory available and use 2/3 of it.
2022-10-20 16:49:19 +02:00
#[ derive(Debug, Clone, Copy, Deserialize, Serialize) ]
2022-09-27 16:33:37 +02:00
pub struct MaxMemory ( Option < Byte > ) ;
impl FromStr for MaxMemory {
type Err = ByteError ;
fn from_str ( s : & str ) -> Result < MaxMemory , ByteError > {
Byte ::from_str ( s ) . map ( Some ) . map ( MaxMemory )
}
}
impl Default for MaxMemory {
fn default ( ) -> MaxMemory {
2022-10-20 18:00:07 +02:00
MaxMemory ( total_memory_bytes ( ) . map ( | bytes | bytes * 2 / 3 ) . map ( Byte ::from_bytes ) )
2022-09-27 16:33:37 +02:00
}
}
impl fmt ::Display for MaxMemory {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
match self . 0 {
Some ( memory ) = > write! ( f , " {} " , memory . get_appropriate_unit ( true ) ) ,
None = > f . write_str ( " unknown " ) ,
}
}
}
impl Deref for MaxMemory {
type Target = Option < Byte > ;
fn deref ( & self ) -> & Self ::Target {
& self . 0
}
}
impl MaxMemory {
pub fn unlimited ( ) -> Self {
Self ( None )
}
}
/// Returns the total amount of bytes available or `None` if this system isn't supported.
fn total_memory_bytes ( ) -> Option < u64 > {
if System ::IS_SUPPORTED {
let memory_kind = RefreshKind ::new ( ) . with_memory ( ) ;
let mut system = System ::new_with_specifics ( memory_kind ) ;
system . refresh_memory ( ) ;
2022-11-28 16:27:41 +01:00
Some ( system . total_memory ( ) )
2022-09-27 16:33:37 +02:00
} else {
None
}
}
2022-10-20 16:49:19 +02:00
#[ derive(Debug, Clone, Copy, Deserialize, Serialize) ]
2022-09-27 16:33:37 +02:00
pub struct MaxThreads ( usize ) ;
impl FromStr for MaxThreads {
type Err = ParseIntError ;
fn from_str ( s : & str ) -> Result < Self , Self ::Err > {
usize ::from_str ( s ) . map ( Self )
}
}
impl Default for MaxThreads {
fn default ( ) -> Self {
MaxThreads ( num_cpus ::get ( ) / 2 )
}
}
impl fmt ::Display for MaxThreads {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
write! ( f , " {} " , self . 0 )
}
}
impl Deref for MaxThreads {
type Target = usize ;
fn deref ( & self ) -> & Self ::Target {
& self . 0
}
}
2021-09-14 18:39:02 +02:00
fn load_certs ( filename : PathBuf ) -> anyhow ::Result < Vec < rustls ::Certificate > > {
2021-09-28 22:22:59 +02:00
let certfile =
fs ::File ::open ( filename ) . map_err ( | _ | anyhow ::anyhow! ( " cannot open certificate file " ) ) ? ;
2020-12-12 13:32:06 +01:00
let mut reader = BufReader ::new ( certfile ) ;
2022-01-21 20:44:17 +00:00
certs ( & mut reader )
. map ( | certs | certs . into_iter ( ) . map ( rustls ::Certificate ) . collect ( ) )
. map_err ( | _ | anyhow ::anyhow! ( " cannot read certificate file " ) )
2020-12-12 13:32:06 +01:00
}
2021-09-14 18:39:02 +02:00
fn load_private_key ( filename : PathBuf ) -> anyhow ::Result < rustls ::PrivateKey > {
2020-12-12 13:32:06 +01:00
let rsa_keys = {
2021-09-28 22:22:59 +02:00
let keyfile = fs ::File ::open ( filename . clone ( ) )
. map_err ( | _ | anyhow ::anyhow! ( " cannot open private key file " ) ) ? ;
2020-12-12 13:32:06 +01:00
let mut reader = BufReader ::new ( keyfile ) ;
2021-09-28 22:22:59 +02:00
rsa_private_keys ( & mut reader )
. map_err ( | _ | anyhow ::anyhow! ( " file contains invalid rsa private key " ) ) ?
2020-12-12 13:32:06 +01:00
} ;
let pkcs8_keys = {
2021-09-28 22:22:59 +02:00
let keyfile = fs ::File ::open ( filename )
. map_err ( | _ | anyhow ::anyhow! ( " cannot open private key file " ) ) ? ;
2020-12-12 13:32:06 +01:00
let mut reader = BufReader ::new ( keyfile ) ;
2021-09-28 22:22:59 +02:00
pkcs8_private_keys ( & mut reader ) . map_err ( | _ | {
anyhow ::anyhow! (
" file contains invalid pkcs8 private key (encrypted keys not supported) "
)
} ) ?
2020-12-12 13:32:06 +01:00
} ;
// prefer to load pkcs8 keys
if ! pkcs8_keys . is_empty ( ) {
2022-01-21 20:44:17 +00:00
Ok ( rustls ::PrivateKey ( pkcs8_keys [ 0 ] . clone ( ) ) )
2020-12-12 13:32:06 +01:00
} else {
assert! ( ! rsa_keys . is_empty ( ) ) ;
2022-01-21 20:44:17 +00:00
Ok ( rustls ::PrivateKey ( rsa_keys [ 0 ] . clone ( ) ) )
2020-12-12 13:32:06 +01:00
}
}
2021-09-14 18:39:02 +02:00
fn load_ocsp ( filename : & Option < PathBuf > ) -> anyhow ::Result < Vec < u8 > > {
2020-12-12 13:32:06 +01:00
let mut ret = Vec ::new ( ) ;
if let Some ( ref name ) = filename {
fs ::File ::open ( name )
2021-09-14 18:39:02 +02:00
. map_err ( | _ | anyhow ::anyhow! ( " cannot open ocsp file " ) ) ?
2020-12-12 13:32:06 +01:00
. read_to_end ( & mut ret )
2021-09-14 18:39:02 +02:00
. map_err ( | _ | anyhow ::anyhow! ( " cannot read oscp file " ) ) ? ;
2020-12-12 13:32:06 +01:00
}
Ok ( ret )
}
2022-03-16 17:01:05 +01:00
2022-10-11 13:24:53 +02:00
/// Checks if the key is defined in the environment variables.
/// If not, inserts it with the given value.
pub fn export_to_env_if_not_present < T > ( key : & str , value : T )
where
T : AsRef < OsStr > ,
{
if let Err ( VarError ::NotPresent ) = std ::env ::var ( key ) {
std ::env ::set_var ( key , value ) ;
}
}
2022-09-07 18:16:33 +02:00
/// Functions used to get default value for `Opt` fields, needs to be function because of serde's default attribute.
2022-09-07 11:51:23 +02:00
fn default_db_path ( ) -> PathBuf {
PathBuf ::from ( DEFAULT_DB_PATH )
}
2022-10-06 22:42:20 -04:00
pub fn default_http_addr ( ) -> String {
2022-09-07 11:51:23 +02:00
DEFAULT_HTTP_ADDR . to_string ( )
}
fn default_env ( ) -> String {
DEFAULT_ENV . to_string ( )
}
fn default_max_index_size ( ) -> Byte {
2022-12-26 11:41:31 +01:00
Byte ::from_bytes ( INDEX_SIZE )
2022-09-07 11:51:23 +02:00
}
fn default_max_task_db_size ( ) -> Byte {
2022-12-26 11:41:31 +01:00
Byte ::from_bytes ( TASK_DB_SIZE )
2022-09-07 11:51:23 +02:00
}
fn default_http_payload_size_limit ( ) -> Byte {
Byte ::from_str ( DEFAULT_HTTP_PAYLOAD_SIZE_LIMIT ) . unwrap ( )
}
fn default_snapshot_dir ( ) -> PathBuf {
PathBuf ::from ( DEFAULT_SNAPSHOT_DIR )
}
2022-12-27 18:17:29 +01:00
fn default_snapshot_interval_sec ( ) -> & 'static str {
DEFAULT_SNAPSHOT_INTERVAL_SEC_STR
2022-09-07 11:51:23 +02:00
}
2022-12-01 15:39:36 +01:00
fn default_dump_dir ( ) -> PathBuf {
PathBuf ::from ( DEFAULT_DUMP_DIR )
2022-09-07 11:51:23 +02:00
}
2022-12-27 18:17:29 +01:00
/// Indicates if a snapshot was scheduled, and if yes with which interval.
#[ derive(Debug, Default, Copy, Clone, Deserialize, Serialize) ]
pub enum ScheduleSnapshot {
/// Scheduled snapshots are disabled.
#[ default ]
Disabled ,
/// Snapshots are scheduled at the specified interval, in seconds.
Enabled ( u64 ) ,
}
impl Display for ScheduleSnapshot {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
match self {
ScheduleSnapshot ::Disabled = > write! ( f , " " ) ,
ScheduleSnapshot ::Enabled ( value ) = > write! ( f , " {} " , value ) ,
}
}
}
impl FromStr for ScheduleSnapshot {
type Err = ParseIntError ;
fn from_str ( s : & str ) -> Result < Self , Self ::Err > {
Ok ( match s {
" " = > ScheduleSnapshot ::Disabled ,
s = > ScheduleSnapshot ::Enabled ( s . parse ( ) ? ) ,
} )
}
}
fn parse_schedule_snapshot ( s : & str ) -> Result < ScheduleSnapshot , ParseIntError > {
Ok ( if s . is_empty ( ) { ScheduleSnapshot ::Disabled } else { ScheduleSnapshot ::from_str ( s ) ? } )
}
fn schedule_snapshot_to_env ( schedule_snapshot : ScheduleSnapshot ) -> Option < String > {
match schedule_snapshot {
ScheduleSnapshot ::Enabled ( snapshot_delay ) = > Some ( snapshot_delay . to_string ( ) ) ,
_ = > None ,
}
}
fn schedule_snapshot_deserialize < ' de , D > ( deserializer : D ) -> Result < ScheduleSnapshot , D ::Error >
where
D : serde ::Deserializer < ' de > ,
{
struct BoolOrInt ;
impl < ' de > serde ::de ::Visitor < ' de > for BoolOrInt {
type Value = ScheduleSnapshot ;
fn expecting ( & self , formatter : & mut fmt ::Formatter ) -> fmt ::Result {
formatter . write_str ( " integer or boolean " )
}
fn visit_bool < E > ( self , value : bool ) -> Result < Self ::Value , E >
where
E : serde ::de ::Error ,
{
Ok ( if value {
ScheduleSnapshot ::Enabled ( DEFAULT_SNAPSHOT_INTERVAL_SEC )
} else {
ScheduleSnapshot ::Disabled
} )
}
fn visit_i64 < E > ( self , v : i64 ) -> Result < Self ::Value , E >
where
E : serde ::de ::Error ,
{
Ok ( ScheduleSnapshot ::Enabled ( v as u64 ) )
}
fn visit_u64 < E > ( self , v : u64 ) -> Result < Self ::Value , E >
where
E : serde ::de ::Error ,
{
Ok ( ScheduleSnapshot ::Enabled ( v ) )
}
fn visit_none < E > ( self ) -> Result < Self ::Value , E >
where
E : serde ::de ::Error ,
{
Ok ( ScheduleSnapshot ::Disabled )
}
fn visit_unit < E > ( self ) -> Result < Self ::Value , E >
where
E : serde ::de ::Error ,
{
Ok ( ScheduleSnapshot ::Disabled )
}
}
deserializer . deserialize_any ( BoolOrInt )
}
2022-03-16 17:01:05 +01:00
#[ cfg(test) ]
mod test {
2022-10-14 14:49:31 +08:00
2022-03-16 17:01:05 +01:00
use super ::* ;
#[ test ]
fn test_valid_opt ( ) {
assert! ( Opt ::try_parse_from ( Some ( " " ) ) . is_ok ( ) ) ;
}
2022-10-12 11:58:28 +08:00
#[ test ]
2022-10-26 13:15:30 +02:00
#[ ignore ]
2022-10-12 11:58:28 +08:00
fn test_meilli_config_file_path_valid ( ) {
temp_env ::with_vars (
2022-12-07 08:20:47 -07:00
vec! [ ( " MEILI_CONFIG_FILE_PATH " , Some ( " ../config.toml " ) ) ] , // Relative path in meilisearch package
2022-10-12 11:58:28 +08:00
| | {
assert! ( Opt ::try_build ( ) . is_ok ( ) ) ;
} ,
) ;
}
#[ test ]
2022-10-26 13:09:17 +02:00
#[ ignore ]
2022-10-12 11:58:28 +08:00
fn test_meilli_config_file_path_invalid ( ) {
2022-10-20 18:00:07 +02:00
temp_env ::with_vars ( vec! [ ( " MEILI_CONFIG_FILE_PATH " , Some ( " ../configgg.toml " ) ) ] , | | {
let possible_error_messages = [
2022-10-14 14:49:31 +08:00
" 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
] ;
2022-10-20 18:00:07 +02:00
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
) ;
} ) ;
2022-10-12 11:58:28 +08:00
}
2022-03-16 17:01:05 +01:00
}