Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 11 additions & 100 deletions crates/apollo_config/src/command.rs
Original file line number Diff line number Diff line change
@@ -1,110 +1,21 @@
use std::collections::BTreeMap;
use std::path::PathBuf;

use clap::{value_parser, Arg, ArgMatches, Command};
use serde_json::{json, Value};

use crate::loading::update_config_map;
use crate::{
ConfigError,
ConfigFormat,
ParamPath,
SerializationType,
SerializedParam,
CONFIG_FILE_ARG_NAME,
CONFIG_FILE_SHORT_ARG_NAME,
CONFIG_FORMAT_ARG_NAME,
};
use crate::{ConfigError, CONFIG_FILE_ARG_NAME, CONFIG_FILE_SHORT_ARG_NAME};

pub(crate) fn get_command_matches(
config_map: &BTreeMap<ParamPath, SerializedParam>,
command: Command,
command_input: Vec<String>,
) -> Result<ArgMatches, ConfigError> {
Ok(command.args(build_args_parser(config_map)).try_get_matches_from(command_input)?)
}

// Takes matched arguments from the command line interface and env variables and updates the config
// map.
// Supports f64, u64, i64, bool and String.
pub(crate) fn update_config_map_by_command_args(
config_map: &mut BTreeMap<ParamPath, Value>,
types_map: &BTreeMap<ParamPath, SerializationType>,
arg_match: &ArgMatches,
) -> Result<(), ConfigError> {
for param_path_id in arg_match.ids() {
let param_path = param_path_id.as_str();
let new_value = get_arg_by_type(types_map, arg_match, param_path)?;
update_config_map(config_map, types_map, param_path, new_value)?;
}
Ok(())
}

// Builds the parser for the command line flags and env variables according to the types of the
// values in the config map.
fn build_args_parser(config_map: &BTreeMap<ParamPath, SerializedParam>) -> Vec<Arg> {
let mut args_parser = vec![
// Custom_config_file_path.
Arg::new(CONFIG_FILE_ARG_NAME)
.long(CONFIG_FILE_ARG_NAME)
.short(CONFIG_FILE_SHORT_ARG_NAME)
.value_delimiter(',')
.help("Optionally sets a config file to use")
.value_parser(value_parser!(PathBuf))
.num_args(1..) // Allow multiple values
.action(clap::ArgAction::Append), // Collect multiple occurrences
// How the --config_file arguments are interpreted. Absent => ConfigFormat::Preset.
Arg::new(CONFIG_FORMAT_ARG_NAME)
.long(CONFIG_FORMAT_ARG_NAME)
.num_args(1)
.value_parser(value_parser!(ConfigFormat))
.help(
"How the --config_file arguments are interpreted: 'preset' (flat dotted-key, \
layered) or 'native' (the first file is nested serde, later files are flat \
secret overrides)",
),
];

for (param_path, serialized_param) in config_map.iter() {
let Some(serialization_type) = serialized_param.content.get_serialization_type() else {
continue; // Pointer target
};
let clap_parser = match serialization_type {
SerializationType::Boolean => clap::value_parser!(bool),
SerializationType::Float => clap::value_parser!(f64).into(),
SerializationType::NegativeInteger => clap::value_parser!(i64).into(),
SerializationType::PositiveInteger => clap::value_parser!(u64).into(),
SerializationType::String => clap::value_parser!(String),
};

let arg = Arg::new(param_path)
.long(param_path)
.env(to_env_var_name(param_path))
.help(&serialized_param.description)
.value_parser(clap_parser)
.allow_negative_numbers(true);

args_parser.push(arg);
}
args_parser
}

// Converts clap arg_matches into json values.
fn get_arg_by_type(
types_map: &BTreeMap<ParamPath, SerializationType>,
arg_match: &ArgMatches,
param_path: &str,
) -> Result<Value, ConfigError> {
let serialization_type = types_map.get(param_path).expect("missing type");
match serialization_type {
SerializationType::Boolean => Ok(json!(arg_match.try_get_one::<bool>(param_path)?)),
SerializationType::Float => Ok(json!(arg_match.try_get_one::<f64>(param_path)?)),
SerializationType::NegativeInteger => Ok(json!(arg_match.try_get_one::<i64>(param_path)?)),
SerializationType::PositiveInteger => Ok(json!(arg_match.try_get_one::<u64>(param_path)?)),
SerializationType::String => Ok(json!(arg_match.try_get_one::<String>(param_path)?)),
}
}

fn to_env_var_name(param_path: &str) -> String {
param_path.replace("#is_none", "__is_none__").to_uppercase().replace('.', "__")
// The config file flag is the only argument; the config files themselves carry every value.
let config_file_arg = Arg::new(CONFIG_FILE_ARG_NAME)
.long(CONFIG_FILE_ARG_NAME)
.short(CONFIG_FILE_SHORT_ARG_NAME)
.value_delimiter(',')
.help("Sets the config files to use")
.value_parser(value_parser!(PathBuf))
.num_args(1..) // Allow multiple values
.action(clap::ArgAction::Append); // Collect multiple occurrences
Ok(command.args([config_file_arg]).try_get_matches_from(command_input)?)
Comment thread
cursor[bot] marked this conversation as resolved.
}
Loading
Loading