Skip to content

Commit eb87e54

Browse files
authored
Merge branch 'PowerShell:main' into reference-doc-compare-and-object
2 parents 4e831bd + db56acc commit eb87e54

File tree

9 files changed

+601
-157
lines changed

9 files changed

+601
-157
lines changed

build.ps1

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ param(
2424
)
2525

2626
$env:RUSTC_LOG=$null
27+
$env:RUSTFLAGS='-Dwarnings'
28+
2729
if ($Verbose) {
2830
$env:RUSTC_LOG='rustc_codegen_ssa::back::link=info'
2931
}
@@ -567,7 +569,11 @@ if ($Test) {
567569
(Get-Module -Name Pester -ListAvailable).Path
568570
}
569571

570-
Invoke-Pester -ErrorAction Stop
572+
try {
573+
Invoke-Pester -ErrorAction Stop
574+
} catch {
575+
throw "Pester had unexpected error: $($_.Exception.Message)"
576+
}
571577
}
572578

573579
function Find-MakeAppx() {

dsc/tests/dsc_expressions.tests.ps1

Lines changed: 210 additions & 154 deletions
Large diffs are not rendered by default.

dsc_lib/locales/en-us.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,10 @@ description = "Encodes a string to Base64 format"
222222
description = "Converts a string or number to a boolean"
223223
invoked = "bool function"
224224

225+
[functions.coalesce]
226+
description = "Returns the first non-null value from a list of arguments"
227+
invoked = "coalesce function"
228+
225229
[functions.concat]
226230
description = "Concatenates two or more strings or arrays"
227231
invoked = "concat function"
@@ -237,6 +241,12 @@ argsMustAllBeIntegers = "Arguments must all be integers"
237241
argsMustAllBeObjects = "Arguments must all be objects"
238242
argsMustAllBeStrings = "Arguments must all be strings"
239243

244+
[functions.createObject]
245+
description = "Creates an object from the given key-value pairs"
246+
invoked = "createObject function"
247+
argsMustBePairs = "Arguments must be provided in key-value pairs"
248+
keyMustBeString = "Object keys must be strings"
249+
240250
[functions.div]
241251
description = "Divides the first number by the second"
242252
invoked = "div function"
@@ -313,6 +323,10 @@ invoked = "mul function"
313323
description = "Negates a boolean value"
314324
invoked = "not function"
315325

326+
[functions.null]
327+
description = "Returns a null value"
328+
invoked = "null function"
329+
316330
[functions.or]
317331
description = "Evaluates if any arguments are true"
318332
invoked = "or function"

dsc_lib/src/functions/coalesce.rs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use crate::DscError;
5+
use crate::configure::context::Context;
6+
use crate::functions::{AcceptedArgKind, Function, FunctionCategory};
7+
use rust_i18n::t;
8+
use serde_json::Value;
9+
use tracing::debug;
10+
11+
#[derive(Debug, Default)]
12+
pub struct Coalesce {}
13+
14+
impl Function for Coalesce {
15+
fn description(&self) -> String {
16+
t!("functions.coalesce.description").to_string()
17+
}
18+
19+
fn category(&self) -> FunctionCategory {
20+
FunctionCategory::Comparison
21+
}
22+
23+
fn min_args(&self) -> usize {
24+
1
25+
}
26+
27+
fn max_args(&self) -> usize {
28+
usize::MAX
29+
}
30+
31+
fn accepted_arg_types(&self) -> Vec<AcceptedArgKind> {
32+
vec![
33+
AcceptedArgKind::Array,
34+
AcceptedArgKind::Boolean,
35+
AcceptedArgKind::Number,
36+
AcceptedArgKind::Object,
37+
AcceptedArgKind::String,
38+
]
39+
}
40+
41+
fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
42+
debug!("{}", t!("functions.coalesce.invoked"));
43+
44+
for arg in args {
45+
if !arg.is_null() {
46+
return Ok(arg.clone());
47+
}
48+
}
49+
50+
Ok(Value::Null)
51+
}
52+
}
53+
54+
#[cfg(test)]
55+
mod tests {
56+
use crate::configure::context::Context;
57+
use crate::parser::Statement;
58+
use super::*;
59+
60+
#[test]
61+
fn direct_function_call_with_nulls() {
62+
let coalesce = Coalesce {};
63+
let context = Context::new();
64+
65+
let args = vec![Value::Null, Value::Null, Value::String("hello".to_string())];
66+
let result = coalesce.invoke(&args, &context).unwrap();
67+
assert_eq!(result, Value::String("hello".to_string()));
68+
69+
let args = vec![Value::Null, Value::Null, Value::Null];
70+
let result = coalesce.invoke(&args, &context).unwrap();
71+
assert_eq!(result, Value::Null);
72+
73+
let args = vec![Value::String("first".to_string()), Value::String("second".to_string())];
74+
let result = coalesce.invoke(&args, &context).unwrap();
75+
assert_eq!(result, Value::String("first".to_string()));
76+
}
77+
78+
#[test]
79+
fn direct_function_call_mixed_types() {
80+
let coalesce = Coalesce {};
81+
let context = Context::new();
82+
83+
let args = vec![Value::Null, serde_json::json!(42), Value::String("fallback".to_string())];
84+
let result = coalesce.invoke(&args, &context).unwrap();
85+
assert_eq!(result, serde_json::json!(42));
86+
87+
let args = vec![Value::Null, Value::Bool(true)];
88+
let result = coalesce.invoke(&args, &context).unwrap();
89+
assert_eq!(result, Value::Bool(true));
90+
}
91+
92+
#[test]
93+
fn direct_function_call_with_arrays() {
94+
let coalesce = Coalesce {};
95+
let context = Context::new();
96+
97+
let first_array = serde_json::json!(["a", "b", "c"]);
98+
let second_array = serde_json::json!(["x", "y", "z"]);
99+
100+
let args = vec![Value::Null, first_array.clone(), second_array];
101+
let result = coalesce.invoke(&args, &context).unwrap();
102+
assert_eq!(result, first_array);
103+
104+
let args = vec![Value::Null, Value::Null, serde_json::json!([1, 2, 3])];
105+
let result = coalesce.invoke(&args, &context).unwrap();
106+
assert_eq!(result, serde_json::json!([1, 2, 3]));
107+
}
108+
109+
#[test]
110+
fn direct_function_call_with_objects() {
111+
let coalesce = Coalesce {};
112+
let context = Context::new();
113+
114+
let first_obj = serde_json::json!({"name": "test", "value": 42});
115+
let second_obj = serde_json::json!({"name": "fallback", "value": 0});
116+
117+
let args = vec![Value::Null, first_obj.clone(), second_obj];
118+
let result = coalesce.invoke(&args, &context).unwrap();
119+
assert_eq!(result, first_obj);
120+
121+
let args = vec![Value::Null, Value::Null, serde_json::json!({"key": "value"})];
122+
let result = coalesce.invoke(&args, &context).unwrap();
123+
assert_eq!(result, serde_json::json!({"key": "value"}));
124+
}
125+
126+
#[test]
127+
fn direct_function_call_with_empty_collections() {
128+
let coalesce = Coalesce {};
129+
let context = Context::new();
130+
131+
let empty_array = serde_json::json!([]);
132+
let args = vec![Value::Null, empty_array.clone(), Value::String("fallback".to_string())];
133+
let result = coalesce.invoke(&args, &context).unwrap();
134+
assert_eq!(result, empty_array);
135+
136+
let empty_obj = serde_json::json!({});
137+
let args = vec![Value::Null, empty_obj.clone(), Value::String("fallback".to_string())];
138+
let result = coalesce.invoke(&args, &context).unwrap();
139+
assert_eq!(result, empty_obj);
140+
}
141+
142+
#[test]
143+
fn parser_with_values() {
144+
let mut parser = Statement::new().unwrap();
145+
let result = parser.parse_and_execute("[coalesce('hello', 'world')]", &Context::new()).unwrap();
146+
assert_eq!(result, "hello");
147+
148+
let result = parser.parse_and_execute("[coalesce(42, 'fallback')]", &Context::new()).unwrap();
149+
assert_eq!(result, 42);
150+
151+
let result = parser.parse_and_execute("[coalesce(true)]", &Context::new()).unwrap();
152+
assert_eq!(result, true);
153+
}
154+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use crate::DscError;
5+
use crate::configure::context::Context;
6+
use crate::functions::{AcceptedArgKind, Function, FunctionCategory};
7+
use rust_i18n::t;
8+
use serde_json::{Map, Value};
9+
use tracing::debug;
10+
11+
#[derive(Debug, Default)]
12+
pub struct CreateObject {}
13+
14+
impl Function for CreateObject {
15+
fn description(&self) -> String {
16+
t!("functions.createObject.description").to_string()
17+
}
18+
19+
fn category(&self) -> FunctionCategory {
20+
FunctionCategory::Object
21+
}
22+
23+
fn min_args(&self) -> usize {
24+
0
25+
}
26+
27+
fn max_args(&self) -> usize {
28+
usize::MAX
29+
}
30+
31+
fn accepted_arg_types(&self) -> Vec<AcceptedArgKind> {
32+
vec![
33+
AcceptedArgKind::Array,
34+
AcceptedArgKind::Boolean,
35+
AcceptedArgKind::Number,
36+
AcceptedArgKind::Object,
37+
AcceptedArgKind::String,
38+
]
39+
}
40+
41+
fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
42+
debug!("{}", t!("functions.createObject.invoked"));
43+
44+
if args.len() % 2 != 0 {
45+
return Err(DscError::Parser(t!("functions.createObject.argsMustBePairs").to_string()));
46+
}
47+
48+
let mut object_result = Map::<String, Value>::new();
49+
50+
for chunk in args.chunks(2) {
51+
let key = &chunk[0];
52+
let value = &chunk[1];
53+
54+
if !key.is_string() {
55+
return Err(DscError::Parser(t!("functions.createObject.keyMustBeString").to_string()));
56+
}
57+
58+
let key_str = key.as_str().unwrap().to_string();
59+
object_result.insert(key_str, value.clone());
60+
}
61+
62+
Ok(Value::Object(object_result))
63+
}
64+
}
65+
66+
#[cfg(test)]
67+
mod tests {
68+
use crate::configure::context::Context;
69+
use crate::parser::Statement;
70+
71+
#[test]
72+
fn simple_object() {
73+
let mut parser = Statement::new().unwrap();
74+
let result = parser.parse_and_execute("[createObject('name', 'test')]", &Context::new()).unwrap();
75+
assert_eq!(result.to_string(), r#"{"name":"test"}"#);
76+
}
77+
78+
#[test]
79+
fn multiple_properties() {
80+
let mut parser = Statement::new().unwrap();
81+
let result = parser.parse_and_execute("[createObject('name', 'test', 'value', 42)]", &Context::new()).unwrap();
82+
assert_eq!(result.to_string(), r#"{"name":"test","value":42}"#);
83+
}
84+
85+
#[test]
86+
fn mixed_value_types() {
87+
let mut parser = Statement::new().unwrap();
88+
let result = parser.parse_and_execute("[createObject('string', 'hello', 'number', 123, 'boolean', true)]", &Context::new()).unwrap();
89+
90+
let json: serde_json::Value = serde_json::from_str(&result.to_string()).unwrap();
91+
assert_eq!(json["string"], "hello");
92+
assert_eq!(json["number"], 123);
93+
assert_eq!(json["boolean"], true);
94+
}
95+
96+
#[test]
97+
fn nested_objects() {
98+
let mut parser = Statement::new().unwrap();
99+
let result = parser.parse_and_execute("[createObject('outer', createObject('inner', 'value'))]", &Context::new()).unwrap();
100+
assert_eq!(result.to_string(), r#"{"outer":{"inner":"value"}}"#);
101+
}
102+
103+
#[test]
104+
fn with_arrays() {
105+
let mut parser = Statement::new().unwrap();
106+
let result = parser.parse_and_execute("[createObject('items', createArray('a', 'b', 'c'))]", &Context::new()).unwrap();
107+
assert_eq!(result.to_string(), r#"{"items":["a","b","c"]}"#);
108+
}
109+
110+
#[test]
111+
fn odd_number_of_args() {
112+
let mut parser = Statement::new().unwrap();
113+
let result = parser.parse_and_execute("[createObject('name')]", &Context::new());
114+
assert!(result.is_err());
115+
}
116+
117+
#[test]
118+
fn non_string_key() {
119+
let mut parser = Statement::new().unwrap();
120+
let result = parser.parse_and_execute("[createObject(123, 'value')]", &Context::new());
121+
assert!(result.is_err());
122+
}
123+
124+
#[test]
125+
fn empty() {
126+
let mut parser = Statement::new().unwrap();
127+
let result = parser.parse_and_execute("[createObject()]", &Context::new()).unwrap();
128+
assert_eq!(result.to_string(), "{}");
129+
}
130+
}

dsc_lib/src/functions/less.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ impl Function for Less {
3434

3535
fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
3636
debug!("{}", t!("functions.less.invoked"));
37-
37+
3838
let first = &args[0];
3939
let second = &args[1];
4040

@@ -83,6 +83,7 @@ mod tests {
8383
assert_eq!(result, true);
8484
}
8585

86+
#[test]
8687
fn type_mismatch_string_number() {
8788
let mut parser = Statement::new().unwrap();
8889
let result = parser.parse_and_execute("[lessOrEquals('5', 3)]", &Context::new());

dsc_lib/src/functions/less_or_equals.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ impl Function for LessOrEquals {
3434

3535
fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
3636
debug!("{}", t!("functions.lessOrEquals.invoked"));
37-
37+
3838
let first = &args[0];
3939
let second = &args[1];
4040

@@ -83,6 +83,7 @@ mod tests {
8383
assert_eq!(result, true);
8484
}
8585

86+
#[test]
8687
fn type_mismatch_string_number() {
8788
let mut parser = Statement::new().unwrap();
8889
let result = parser.parse_and_execute("[lessOrEquals('5', 3)]", &Context::new());

0 commit comments

Comments
 (0)