Skip to content

Commit d64d584

Browse files
Steve Lee (POWERSHELL HE/HIM) (from Dev Box)SteveL-MSFT
authored andcommitted
add empty and length functions
1 parent 06b8a4d commit d64d584

File tree

5 files changed

+247
-0
lines changed

5 files changed

+247
-0
lines changed

dsc/tests/dsc_functions.tests.ps1

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,87 @@ Describe 'tests for function expressions' {
159159
($out.results[0].result.actualState.output | Out-String) | Should -BeExactly ($expected | Out-String)
160160
}
161161
}
162+
163+
It 'length function works for: <expression>' -TestCases @(
164+
@{ expression = "[length(parameters('array'))]" ; expected = 3 }
165+
@{ expression = "[length(parameters('object'))]" ; expected = 4 }
166+
@{ expression = "[length(parameters('string'))]" ; expected = 12 }
167+
@{ expression = "[length('')]"; expected = 0 }
168+
) {
169+
param($expression, $expected, $isError)
170+
171+
$config_yaml = @"
172+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
173+
parameters:
174+
array:
175+
type: array
176+
defaultValue:
177+
- a
178+
- b
179+
- c
180+
object:
181+
type: object
182+
defaultValue:
183+
one: a
184+
two: b
185+
three: c
186+
four: d
187+
string:
188+
type: string
189+
defaultValue: 'hello world!'
190+
resources:
191+
- name: Echo
192+
type: Microsoft.DSC.Debug/Echo
193+
properties:
194+
output: "$expression"
195+
"@
196+
$out = dsc -l trace config get -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json
197+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw)
198+
($out.results[0].result.actualState.output | Out-String) | Should -BeExactly ($expected | Out-String)
199+
}
200+
201+
It 'empty function works for: <expression>' -TestCases @(
202+
@{ expression = "[empty(parameters('array'))]" ; expected = $false }
203+
@{ expression = "[empty(parameters('object'))]" ; expected = $false }
204+
@{ expression = "[empty(parameters('string'))]" ; expected = $false }
205+
@{ expression = "[empty(parameters('emptyArray'))]" ; expected = $true }
206+
@{ expression = "[empty(parameters('emptyObject'))]" ; expected = $true }
207+
@{ expression = "[empty('')]" ; expected = $true }
208+
) {
209+
param($expression, $expected)
210+
211+
$config_yaml = @"
212+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
213+
parameters:
214+
array:
215+
type: array
216+
defaultValue:
217+
- a
218+
- b
219+
- c
220+
emptyArray:
221+
type: array
222+
defaultValue: []
223+
object:
224+
type: object
225+
defaultValue:
226+
one: a
227+
two: b
228+
three: c
229+
emptyObject:
230+
type: object
231+
defaultValue: {}
232+
string:
233+
type: string
234+
defaultValue: 'hello world!'
235+
resources:
236+
- name: Echo
237+
type: Microsoft.DSC.Debug/Echo
238+
properties:
239+
output: "$expression"
240+
"@
241+
$out = dsc -l trace config get -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json
242+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw)
243+
($out.results[0].result.actualState.output | Out-String) | Should -BeExactly ($expected | Out-String)
244+
}
162245
}

dsc_lib/locales/en-us.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,11 @@ description = "Divides the first number by the second"
259259
invoked = "div function"
260260
divideByZero = "Cannot divide by zero"
261261

262+
[functions.empty]
263+
description = "Checks if an array, object, or string is empty"
264+
invoked = "empty function"
265+
invalidArgType = "Invalid argument type, argument must be an array, object, or string"
266+
262267
[functions.envvar]
263268
description = "Retrieves the value of an environment variable"
264269
notFound = "Environment variable not found"
@@ -297,6 +302,11 @@ parseStringError = "unable to parse string to int"
297302
castError = "unable to cast to int"
298303
parseNumError = "unable to parse number to int"
299304

305+
[functions.length]
306+
description = "Returns the length of a string, array, or object"
307+
invoked = "length function"
308+
invalidArgType = "Invalid argument type, argument must be a string, array, or object"
309+
300310
[functions.less]
301311
description = "Evaluates if the first value is less than the second value"
302312
invoked = "less function"

dsc_lib/src/functions/empty.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use core::option::Option::Some;
5+
6+
use crate::DscError;
7+
use crate::configure::context::Context;
8+
use crate::functions::{AcceptedArgKind, Function, FunctionCategory};
9+
use rust_i18n::t;
10+
use serde_json::Value;
11+
use tracing::debug;
12+
13+
#[derive(Debug, Default)]
14+
pub struct Empty {}
15+
16+
impl Function for Empty {
17+
fn description(&self) -> String {
18+
t!("functions.empty.description").to_string()
19+
}
20+
21+
fn category(&self) -> FunctionCategory {
22+
FunctionCategory::Array
23+
}
24+
25+
fn min_args(&self) -> usize {
26+
1
27+
}
28+
29+
fn max_args(&self) -> usize {
30+
1
31+
}
32+
33+
fn accepted_arg_types(&self) -> Vec<AcceptedArgKind> {
34+
vec![AcceptedArgKind::Array, AcceptedArgKind::Object, AcceptedArgKind::String]
35+
}
36+
37+
fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
38+
debug!("{}", t!("functions.empty.invoked"));
39+
if let Some(array) = args[0].as_array() {
40+
return Ok(Value::Bool(array.is_empty()));
41+
}
42+
43+
if let Some(object) = args[0].as_object() {
44+
return Ok(Value::Bool(object.keys().len() == 0));
45+
}
46+
47+
if let Some(string) = args[0].as_str() {
48+
return Ok(Value::Bool(string.is_empty()));
49+
}
50+
51+
Err(DscError::Parser(t!("functions.empty.invalidArgType").to_string()))
52+
}
53+
}
54+
55+
56+
#[cfg(test)]
57+
mod tests {
58+
use crate::configure::context::Context;
59+
use crate::parser::Statement;
60+
use serde_json::Value;
61+
62+
#[test]
63+
fn empty_string() {
64+
let mut parser = Statement::new().unwrap();
65+
let result = parser.parse_and_execute("[empty('')]", &Context::new()).unwrap();
66+
assert_eq!(result, Value::Bool(true));
67+
}
68+
69+
#[test]
70+
fn not_empty_string() {
71+
let mut parser = Statement::new().unwrap();
72+
let result = parser.parse_and_execute("[empty('foo')]", &Context::new()).unwrap();
73+
assert_eq!(result, Value::Bool(false));
74+
}
75+
}

dsc_lib/src/functions/length.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use core::option::Option::Some;
5+
6+
use crate::DscError;
7+
use crate::configure::context::Context;
8+
use crate::functions::{AcceptedArgKind, Function, FunctionCategory};
9+
use rust_i18n::t;
10+
use serde_json::Value;
11+
use tracing::debug;
12+
13+
#[derive(Debug, Default)]
14+
pub struct Length {}
15+
16+
impl Function for Length {
17+
fn description(&self) -> String {
18+
t!("functions.length.description").to_string()
19+
}
20+
21+
fn category(&self) -> FunctionCategory {
22+
FunctionCategory::Array
23+
}
24+
25+
fn min_args(&self) -> usize {
26+
1
27+
}
28+
29+
fn max_args(&self) -> usize {
30+
1
31+
}
32+
33+
fn accepted_arg_types(&self) -> Vec<AcceptedArgKind> {
34+
vec![AcceptedArgKind::Array, AcceptedArgKind::Object, AcceptedArgKind::String]
35+
}
36+
37+
fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
38+
debug!("{}", t!("functions.length.invoked"));
39+
if let Some(array) = args[0].as_array() {
40+
return Ok(Value::Number(array.len().into()));
41+
}
42+
43+
if let Some(object) = args[0].as_object() {
44+
return Ok(Value::Number(object.keys().len().into()));
45+
}
46+
47+
if let Some(string) = args[0].as_str() {
48+
return Ok(Value::Number(string.len().into()));
49+
}
50+
51+
Err(DscError::Parser(t!("functions.length.invalidArgType").to_string()))
52+
}
53+
}
54+
55+
56+
#[cfg(test)]
57+
mod tests {
58+
use crate::configure::context::Context;
59+
use crate::parser::Statement;
60+
use serde_json::Value;
61+
62+
#[test]
63+
fn empty_string() {
64+
let mut parser = Statement::new().unwrap();
65+
let result = parser.parse_and_execute("[length('')]", &Context::new()).unwrap();
66+
assert_eq!(result, Value::Number(0.into()));
67+
}
68+
69+
#[test]
70+
fn not_empty_string() {
71+
let mut parser = Statement::new().unwrap();
72+
let result = parser.parse_and_execute("[length('foo')]", &Context::new()).unwrap();
73+
assert_eq!(result, Value::Number(3.into()));
74+
}
75+
}

dsc_lib/src/functions/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ pub mod contains;
2121
pub mod create_array;
2222
pub mod create_object;
2323
pub mod div;
24+
pub mod empty;
2425
pub mod envvar;
2526
pub mod equals;
2627
pub mod greater;
2728
pub mod greater_or_equals;
2829
pub mod r#if;
2930
pub mod r#false;
31+
pub mod length;
3032
pub mod less;
3133
pub mod less_or_equals;
3234
pub mod format;
@@ -101,6 +103,7 @@ impl FunctionDispatcher {
101103
functions.insert("createArray".to_string(), Box::new(create_array::CreateArray{}));
102104
functions.insert("createObject".to_string(), Box::new(create_object::CreateObject{}));
103105
functions.insert("div".to_string(), Box::new(div::Div{}));
106+
functions.insert("empty".to_string(), Box::new(empty::Empty{}));
104107
functions.insert("envvar".to_string(), Box::new(envvar::Envvar{}));
105108
functions.insert("equals".to_string(), Box::new(equals::Equals{}));
106109
functions.insert("false".to_string(), Box::new(r#false::False{}));
@@ -109,6 +112,7 @@ impl FunctionDispatcher {
109112
functions.insert("if".to_string(), Box::new(r#if::If{}));
110113
functions.insert("format".to_string(), Box::new(format::Format{}));
111114
functions.insert("int".to_string(), Box::new(int::Int{}));
115+
functions.insert("length".to_string(), Box::new(length::Length{}));
112116
functions.insert("less".to_string(), Box::new(less::Less{}));
113117
functions.insert("lessOrEquals".to_string(), Box::new(less_or_equals::LessOrEquals{}));
114118
functions.insert("max".to_string(), Box::new(max::Max{}));

0 commit comments

Comments
 (0)