Skip to content

Commit b38dacf

Browse files
committed
Add parameter merging functionality with inline precedence
1 parent bc5639b commit b38dacf

File tree

5 files changed

+445
-21
lines changed

5 files changed

+445
-21
lines changed

dsc/locales/en-us.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ failedReadingParametersFile = "Failed to read parameters file"
4444
readingParametersFromStdin = "Reading parameters from STDIN"
4545
generatingCompleter = "Generating completion script for"
4646
readingParametersFile = "Reading parameters from file"
47+
mergingParameters = "Merging inline parameters with parameters file (inline takes precedence)"
48+
failedMergingParameters = "Failed to merge parameters"
4749
usingDscVersion = "Running DSC version"
4850
foundProcesses = "Found processes"
4951
failedToGetPid = "Could not get current process id"

dsc/src/args.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ pub enum SubCommand {
6666
Config {
6767
#[clap(subcommand)]
6868
subcommand: ConfigSubCommand,
69-
#[clap(short, long, help = t!("args.parameters").to_string(), conflicts_with = "parameters_file")]
69+
#[clap(short, long, help = t!("args.parameters").to_string())]
7070
parameters: Option<String>,
71-
#[clap(short = 'f', long, help = t!("args.parametersFile").to_string(), conflicts_with = "parameters")]
71+
#[clap(short = 'f', long, help = t!("args.parametersFile").to_string())]
7272
parameters_file: Option<String>,
7373
#[clap(short = 'r', long, help = t!("args.systemRoot").to_string())]
7474
system_root: Option<String>,

dsc/src/main.rs

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,63 @@ fn main() {
5454
generate(shell, &mut cmd, "dsc", &mut io::stdout());
5555
},
5656
SubCommand::Config { subcommand, parameters, parameters_file, system_root, as_group, as_assert, as_include } => {
57-
if let Some(file_name) = parameters_file {
57+
let merged_parameters = if parameters_file.is_some() && parameters.is_some() {
58+
// Both parameters and parameters_file provided - merge them with inline taking precedence
59+
let file_params = if let Some(file_name) = &parameters_file {
60+
if file_name == "-" {
61+
info!("{}", t!("main.readingParametersFromStdin"));
62+
let mut stdin = Vec::<u8>::new();
63+
match io::stdin().read_to_end(&mut stdin) {
64+
Ok(_) => {
65+
match String::from_utf8(stdin) {
66+
Ok(input) => Some(input),
67+
Err(err) => {
68+
error!("{}: {err}", t!("util.invalidUtf8"));
69+
exit(EXIT_INVALID_INPUT);
70+
}
71+
}
72+
},
73+
Err(err) => {
74+
error!("{}: {err}", t!("util.failedToReadStdin"));
75+
exit(EXIT_INVALID_INPUT);
76+
}
77+
}
78+
} else {
79+
info!("{}: {file_name}", t!("main.readingParametersFile"));
80+
match std::fs::read_to_string(file_name) {
81+
Ok(content) => Some(content),
82+
Err(err) => {
83+
error!("{} '{file_name}': {err}", t!("main.failedReadingParametersFile"));
84+
exit(util::EXIT_INVALID_INPUT);
85+
}
86+
}
87+
}
88+
} else {
89+
None
90+
};
91+
92+
// Parse both and merge
93+
if let (Some(file_content), Some(inline_content)) = (file_params, parameters.as_ref()) {
94+
info!("{}", t!("main.mergingParameters"));
95+
match util::merge_parameters(&file_content, inline_content) {
96+
Ok(merged) => Some(merged),
97+
Err(err) => {
98+
error!("{}: {err}", t!("main.failedMergingParameters"));
99+
exit(EXIT_INVALID_INPUT);
100+
}
101+
}
102+
} else {
103+
parameters.clone()
104+
}
105+
} else if let Some(file_name) = parameters_file {
106+
// Only parameters_file provided
58107
if file_name == "-" {
59108
info!("{}", t!("main.readingParametersFromStdin"));
60109
let mut stdin = Vec::<u8>::new();
61-
let parameters = match io::stdin().read_to_end(&mut stdin) {
110+
match io::stdin().read_to_end(&mut stdin) {
62111
Ok(_) => {
63112
match String::from_utf8(stdin) {
64-
Ok(input) => {
65-
input
66-
},
113+
Ok(input) => Some(input),
67114
Err(err) => {
68115
error!("{}: {err}", t!("util.invalidUtf8"));
69116
exit(EXIT_INVALID_INPUT);
@@ -74,22 +121,22 @@ fn main() {
74121
error!("{}: {err}", t!("util.failedToReadStdin"));
75122
exit(EXIT_INVALID_INPUT);
76123
}
77-
};
78-
subcommand::config(&subcommand, &Some(parameters), true, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format);
79-
return;
80-
}
81-
info!("{}: {file_name}", t!("main.readingParametersFile"));
82-
match std::fs::read_to_string(&file_name) {
83-
Ok(parameters) => subcommand::config(&subcommand, &Some(parameters), false, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format),
84-
Err(err) => {
85-
error!("{} '{file_name}': {err}", t!("main.failedReadingParametersFile"));
86-
exit(util::EXIT_INVALID_INPUT);
124+
}
125+
} else {
126+
info!("{}: {file_name}", t!("main.readingParametersFile"));
127+
match std::fs::read_to_string(&file_name) {
128+
Ok(content) => Some(content),
129+
Err(err) => {
130+
error!("{} '{file_name}': {err}", t!("main.failedReadingParametersFile"));
131+
exit(util::EXIT_INVALID_INPUT);
132+
}
87133
}
88134
}
89-
}
90-
else {
91-
subcommand::config(&subcommand, &parameters, false, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format);
92-
}
135+
} else {
136+
parameters
137+
};
138+
139+
subcommand::config(&subcommand, &merged_parameters, false, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format);
93140
},
94141
SubCommand::Extension { subcommand } => {
95142
subcommand::extension(&subcommand, progress_format);

dsc/src/util.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,3 +582,87 @@ pub fn in_desired_state(test_result: &ResourceTestResult) -> bool {
582582
}
583583
}
584584
}
585+
586+
/// Merge two parameter sets, with inline parameters taking precedence over file parameters.
587+
///
588+
/// # Arguments
589+
///
590+
/// * `file_params` - Parameters from file (JSON or YAML format)
591+
/// * `inline_params` - Inline parameters (JSON or YAML format) that take precedence
592+
///
593+
/// # Returns
594+
///
595+
/// * `Result<String, DscError>` - Merged parameters as JSON string
596+
///
597+
/// # Errors
598+
///
599+
/// This function will return an error if:
600+
/// - Either parameter set cannot be parsed as valid JSON or YAML
601+
/// - The merged result cannot be serialized to JSON
602+
pub fn merge_parameters(file_params: &str, inline_params: &str) -> Result<String, DscError> {
603+
use serde_json::Value;
604+
605+
// Parse file parameters
606+
let file_value: Value = match serde_json::from_str(file_params) {
607+
Ok(json) => json,
608+
Err(_) => {
609+
// YAML
610+
match serde_yaml::from_str::<serde_yaml::Value>(file_params) {
611+
Ok(yaml) => serde_json::to_value(yaml)?,
612+
Err(err) => {
613+
return Err(DscError::Parser(format!("Failed to parse file parameters: {err}")));
614+
}
615+
}
616+
}
617+
};
618+
619+
// Parse inline parameters
620+
let inline_value: Value = match serde_json::from_str(inline_params) {
621+
Ok(json) => json,
622+
Err(_) => {
623+
// YAML
624+
match serde_yaml::from_str::<serde_yaml::Value>(inline_params) {
625+
Ok(yaml) => serde_json::to_value(yaml)?,
626+
Err(err) => {
627+
return Err(DscError::Parser(format!("Failed to parse inline parameters: {err}")));
628+
}
629+
}
630+
}
631+
};
632+
633+
// Both must be objects to merge
634+
let Some(mut file_obj) = file_value.as_object().cloned() else {
635+
return Err(DscError::Parser("File parameters must be a JSON object".to_string()));
636+
};
637+
638+
let Some(inline_obj) = inline_value.as_object() else {
639+
return Err(DscError::Parser("Inline parameters must be a JSON object".to_string()));
640+
};
641+
642+
// Special handling for the "parameters" key - merge nested objects
643+
if let (Some(file_params_value), Some(inline_params_value)) = (file_obj.get("parameters"), inline_obj.get("parameters")) {
644+
if let (Some(mut file_params_obj), Some(inline_params_obj)) = (file_params_value.as_object().cloned(), inline_params_value.as_object()) {
645+
// Merge the nested parameters objects
646+
for (key, value) in inline_params_obj {
647+
file_params_obj.insert(key.clone(), value.clone());
648+
}
649+
file_obj.insert("parameters".to_string(), Value::Object(file_params_obj));
650+
} else {
651+
// If one is not an object, inline takes precedence
652+
file_obj.insert("parameters".to_string(), inline_params_value.clone());
653+
}
654+
} else if let Some(inline_params_value) = inline_obj.get("parameters") {
655+
// Only inline has parameters
656+
file_obj.insert("parameters".to_string(), inline_params_value.clone());
657+
}
658+
659+
// Merge other top-level keys: inline parameters override file parameters
660+
for (key, value) in inline_obj {
661+
if key != "parameters" {
662+
file_obj.insert(key.clone(), value.clone());
663+
}
664+
}
665+
666+
let merged = Value::Object(file_obj);
667+
Ok(serde_json::to_string(&merged)?)
668+
}

0 commit comments

Comments
 (0)