Skip to content
Merged
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
48 changes: 48 additions & 0 deletions dsc/tests/dsc_expressions.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,32 @@ resources:
@{ expression = "[equals('a', 'a')]"; expected = $true }
@{ expression = "[equals('a', 'b')]"; expected = $false }
@{ expression = "[not(equals('a', 'b'))]"; expected = $true }
@{ expression = "[greater(5, 3)]"; expected = $true }
@{ expression = "[greater(3, 5)]"; expected = $false }
@{ expression = "[greater(5, 5)]"; expected = $false }
@{ expression = "[greaterOrEquals(5, 3)]"; expected = $true }
@{ expression = "[greaterOrEquals(3, 5)]"; expected = $false }
@{ expression = "[greaterOrEquals(5, 5)]"; expected = $true }
@{ expression = "[less(3, 5)]"; expected = $true }
@{ expression = "[less(5, 3)]"; expected = $false }
@{ expression = "[less(5, 5)]"; expected = $false }
@{ expression = "[lessOrEquals(3, 5)]"; expected = $true }
@{ expression = "[lessOrEquals(5, 3)]"; expected = $false }
@{ expression = "[lessOrEquals(5, 5)]"; expected = $true }
@{ expression = "[greater('b', 'a')]"; expected = $true }
@{ expression = "[greater('a', 'b')]"; expected = $false }
@{ expression = "[greater('A', 'a')]"; expected = $false }
@{ expression = "[greaterOrEquals('b', 'a')]"; expected = $true }
@{ expression = "[greaterOrEquals('a', 'b')]"; expected = $false }
@{ expression = "[greaterOrEquals('a', 'a')]"; expected = $true }
@{ expression = "[greaterOrEquals('Aa', 'aa')]"; expected = $false }
@{ expression = "[less('a', 'b')]"; expected = $true }
@{ expression = "[less('b', 'a')]"; expected = $false }
@{ expression = "[less('A', 'a')]"; expected = $true }
@{ expression = "[lessOrEquals('a', 'b')]"; expected = $true }
@{ expression = "[lessOrEquals('b', 'a')]"; expected = $false }
@{ expression = "[lessOrEquals('a', 'a')]"; expected = $true }
@{ expression = "[lessOrEquals('aa', 'Aa')]"; expected = $false }
@{ expression = "[and(true, true)]"; expected = $true }
@{ expression = "[and(true, false)]"; expected = $false }
@{ expression = "[or(false, true)]"; expected = $true }
Expand All @@ -138,4 +164,26 @@ resources:
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw | Out-String)
$out.results[0].result.actualState.output | Should -Be $expected -Because ($out | ConvertTo-Json -Depth 10| Out-String)
}

It 'Comparison functions handle type mismatches: <expression>' -TestCases @(
@{ expression = "[greater('a', 1)]" }
@{ expression = "[greaterOrEquals('5', 3)]" }
@{ expression = "[less(1, 'b')]" }
@{ expression = "[lessOrEquals(5, 'a')]" }
) {
param($expression)
$yaml = @"
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: echo
type: Microsoft.DSC.Debug/Echo
properties:
output: "$expression"
"@
$out = dsc config get -i $yaml 2>$TestDrive/error.log
$LASTEXITCODE | Should -Be 2
$log = Get-Content -Path $TestDrive/error.log -Raw
$log | Should -BeLike "*ERROR* Arguments must be of the same type*"

}
}
17 changes: 17 additions & 0 deletions dsc_lib/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ extensionManifestSchemaDescription = "Defines the JSON Schema the extension mani
[functions]
invalidArgType = "Invalid argument type"
invalidArguments = "Invalid argument(s)"
typeMismatch = "Arguments must be of the same type (both numbers or both strings)"
unknownFunction = "Unknown function '%{name}'"
noArgsAccepted = "Function '%{name}' does not accept arguments"
invalidArgCount = "Function '%{name}' requires exactly %{count} arguments"
Expand Down Expand Up @@ -248,6 +249,14 @@ description = "Evaluates if the two values are the same"
description = "Returns the boolean value false"
invoked = "false function"

[functions.greater]
description = "Evaluates if the first value is greater than the second value"
invoked = "greater function"

[functions.greaterOrEquals]
description = "Evaluates if the first value is greater than or equal to the second value"
invoked = "greaterOrEquals function"

[functions.format]
description = "Formats a string using the given arguments"
experimental = "`format()` function is experimental"
Expand All @@ -267,6 +276,14 @@ parseStringError = "unable to parse string to int"
castError = "unable to cast to int"
parseNumError = "unable to parse number to int"

[functions.less]
description = "Evaluates if the first value is less than the second value"
invoked = "less function"

[functions.lessOrEquals]
description = "Evaluates if the first value is less than or equal to the second value"
invoked = "lessOrEquals function"

[functions.max]
description = "Returns the largest number from a list of numbers"
emptyArray = "Array cannot be empty"
Expand Down
93 changes: 93 additions & 0 deletions dsc_lib/src/functions/greater.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::DscError;
use crate::configure::context::Context;
use crate::functions::{AcceptedArgKind, Function, FunctionCategory};
use rust_i18n::t;
use serde_json::Value;
use tracing::debug;

#[derive(Debug, Default)]
pub struct Greater {}

impl Function for Greater {
fn description(&self) -> String {
t!("functions.greater.description").to_string()
}

fn category(&self) -> FunctionCategory {
FunctionCategory::Comparison
}

fn accepted_arg_types(&self) -> Vec<AcceptedArgKind> {
vec![AcceptedArgKind::Number, AcceptedArgKind::String]
}

fn min_args(&self) -> usize {
2
}

fn max_args(&self) -> usize {
2
}

fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
debug!("{}", t!("functions.greater.invoked"));

let first = &args[0];
let second = &args[1];

if let (Some(num1), Some(num2)) = (first.as_i64(), second.as_i64()) {
return Ok(Value::Bool(num1 > num2));
}

if let (Some(str1), Some(str2)) = (first.as_str(), second.as_str()) {
return Ok(Value::Bool(str1 > str2));
}

Err(DscError::Parser(t!("functions.typeMismatch").to_string()))
}
}

#[cfg(test)]
mod tests {
use crate::configure::context::Context;
use crate::parser::Statement;

#[test]
fn number_greater() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[greater(2,1)]", &Context::new()).unwrap();
assert_eq!(result, true);
}

#[test]
fn number_not_greater() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[greater(1,2)]", &Context::new()).unwrap();
assert_eq!(result, false);
}

#[test]
fn number_equal() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[greater(1,1)]", &Context::new()).unwrap();
assert_eq!(result, false);
}

#[test]
fn string_greater() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[greater('b','a')]", &Context::new()).unwrap();
assert_eq!(result, true);
}

#[test]
fn type_mismatch_string_number() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[greater('5', 3)]", &Context::new());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Arguments must be of the same type"));
}
}
93 changes: 93 additions & 0 deletions dsc_lib/src/functions/greater_or_equals.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::DscError;
use crate::configure::context::Context;
use crate::functions::{AcceptedArgKind, Function, FunctionCategory};
use rust_i18n::t;
use serde_json::Value;
use tracing::debug;

#[derive(Debug, Default)]
pub struct GreaterOrEquals {}

impl Function for GreaterOrEquals {
fn description(&self) -> String {
t!("functions.greaterOrEquals.description").to_string()
}

fn category(&self) -> FunctionCategory {
FunctionCategory::Comparison
}

fn min_args(&self) -> usize {
2
}

fn max_args(&self) -> usize {
2
}

fn accepted_arg_types(&self) -> Vec<AcceptedArgKind> {
vec![AcceptedArgKind::Number, AcceptedArgKind::String]
}

fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
debug!("{}", t!("functions.greaterOrEquals.invoked"));

let first = &args[0];
let second = &args[1];

if let (Some(num1), Some(num2)) = (first.as_i64(), second.as_i64()) {
return Ok(Value::Bool(num1 >= num2));
}

if let (Some(str1), Some(str2)) = (first.as_str(), second.as_str()) {
return Ok(Value::Bool(str1 >= str2));
}

Err(DscError::Parser(t!("functions.typeMismatch").to_string()))
}
}

#[cfg(test)]
mod tests {
use crate::configure::context::Context;
use crate::parser::Statement;

#[test]
fn number_greater_or_equals() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[greaterOrEquals(5,3)]", &Context::new()).unwrap();
assert_eq!(result, true);
}

#[test]
fn number_not_greater_or_equals() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[greaterOrEquals(3,5)]", &Context::new()).unwrap();
assert_eq!(result, false);
}

#[test]
fn number_equal() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[greaterOrEquals(5,5)]", &Context::new()).unwrap();
assert_eq!(result, true);
}

#[test]
fn string_greater_or_equals() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[greaterOrEquals('b','a')]", &Context::new()).unwrap();
assert_eq!(result, true);
}

#[test]
fn type_mismatch_string_number() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[greaterOrEquals('5', 3)]", &Context::new());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Arguments must be of the same type"));
}
}
92 changes: 92 additions & 0 deletions dsc_lib/src/functions/less.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::DscError;
use crate::configure::context::Context;
use crate::functions::{AcceptedArgKind, Function, FunctionCategory};
use rust_i18n::t;
use serde_json::Value;
use tracing::debug;

#[derive(Debug, Default)]
pub struct Less {}

impl Function for Less {
fn description(&self) -> String {
t!("functions.less.description").to_string()
}

fn category(&self) -> FunctionCategory {
FunctionCategory::Comparison
}

fn min_args(&self) -> usize {
2
}

fn max_args(&self) -> usize {
2
}

fn accepted_arg_types(&self) -> Vec<AcceptedArgKind> {
vec![AcceptedArgKind::Number, AcceptedArgKind::String]
}

fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
debug!("{}", t!("functions.less.invoked"));

let first = &args[0];
let second = &args[1];

if let (Some(num1), Some(num2)) = (first.as_i64(), second.as_i64()) {
return Ok(Value::Bool(num1 < num2));
}

if let (Some(str1), Some(str2)) = (first.as_str(), second.as_str()) {
return Ok(Value::Bool(str1 < str2));
}

Err(DscError::Parser(t!("functions.typeMismatch").to_string()))
}
}

#[cfg(test)]
mod tests {
use crate::configure::context::Context;
use crate::parser::Statement;

#[test]
fn number_less() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[less(3,5)]", &Context::new()).unwrap();
assert_eq!(result, true);
}

#[test]
fn number_not_less() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[less(5,3)]", &Context::new()).unwrap();
assert_eq!(result, false);
}

#[test]
fn number_equal() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[less(5,5)]", &Context::new()).unwrap();
assert_eq!(result, false);
}

#[test]
fn string_less() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[less('a','b')]", &Context::new()).unwrap();
assert_eq!(result, true);
}

fn type_mismatch_string_number() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[lessOrEquals('5', 3)]", &Context::new());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Arguments must be of the same type"));
}
}
Loading
Loading