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
31 changes: 31 additions & 0 deletions dsc/tests/dsc_expressions.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,35 @@ resources:
$LASTEXITCODE | Should -Be 0
$out.results[0].result[1].result.actualState.output.family | Should -BeExactly $out.results[0].result[0].result.actualState.family
}

It 'Logical functions work: <expression>' -TestCases @(
@{ expression = "[equals('a', 'a')]"; expected = $true }
@{ expression = "[equals('a', 'b')]"; expected = $false }
@{ expression = "[not(equals('a', 'b'))]"; expected = $true }
@{ expression = "[and(true, true)]"; expected = $true }
@{ expression = "[and(true, false)]"; expected = $false }
@{ expression = "[or(false, true)]"; expected = $true }
@{ expression = "[or(false, false)]"; expected = $false }
@{ expression = "[not(true)]"; expected = $false }
@{ expression = "[not(or(true, false))]"; expected = $false }
@{ expression = "[bool('TRUE')]" ; expected = $true }
@{ expression = "[bool('False')]" ; expected = $false }
@{ expression = "[bool(1)]" ; expected = $true }
@{ expression = "[not(bool(0))]" ; expected = $true }
@{ expression = "[true()]" ; expected = $true }
@{ expression = "[false()]" ; expected = $false }
) {
param($expression, $expected)
$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 | ConvertFrom-Json
$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)
}
}
24 changes: 24 additions & 0 deletions dsc_lib/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,17 @@ noStringArgs = "Function '%{name}' does not accept string arguments, accepted ty
description = "Adds two or more numbers together"
invoked = "add function"

[functions.and]
description = "Evaluates if all arguments are true"
invoked = "and function"

[functions.base64]
description = "Encodes a string to Base64 format"

[functions.bool]
description = "Converts a string or number to a boolean"
invoked = "bool function"

[functions.concat]
description = "Concatenates two or more strings or arrays"
invoked = "concat function"
Expand Down Expand Up @@ -234,6 +242,10 @@ notFound = "Environment variable not found"
[functions.equals]
description = "Evaluates if the two values are the same"

[functions.false]
description = "Returns the boolean value false"
invoked = "false function"

[functions.format]
description = "Formats a string using the given arguments"
experimental = "`format()` function is experimental"
Expand Down Expand Up @@ -274,6 +286,14 @@ divideByZero = "Cannot divide by zero"
description = "Multiplies two or more numbers together"
invoked = "mul function"

[functions.not]
description = "Negates a boolean value"
invoked = "not function"

[functions.or]
description = "Evaluates if any arguments are true"
invoked = "or function"

[functions.parameters]
description = "Retrieves parameters from the configuration"
invoked = "parameters function"
Expand Down Expand Up @@ -314,6 +334,10 @@ invoked = "sub function"
description = "Returns the system root path"
invoked = "systemRoot function"

[functions.true]
description = "Returns the boolean value true"
invoked = "true function"

[functions.variables]
description = "Retrieves the value of a variable"
invoked = "variables function"
Expand Down
82 changes: 82 additions & 0 deletions dsc_lib/src/functions/and.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// 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 And {}

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

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

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

fn max_args(&self) -> usize {
usize::MAX
}

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

fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
debug!("{}", t!("functions.and.invoked"));
for arg in args {
if let Some(value) = arg.as_bool() {
if !value {
return Ok(Value::Bool(false));
}
} else {
return Err(DscError::Parser(t!("functions.invalidArguments").to_string()));
}
}
Ok(Value::Bool(true))
}
}

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

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

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

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

#[test]
fn all_true() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[and(true, true)]", &Context::new()).unwrap();
assert_eq!(result, true);
}
}
83 changes: 83 additions & 0 deletions dsc_lib/src/functions/bool.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// 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 Bool {}

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

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

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

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

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

fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
debug!("{}", t!("functions.bool.invoked"));
if let Some(arg) = args[0].as_str() {
match arg.to_lowercase().as_str() {
"true" => Ok(Value::Bool(true)),
"false" => Ok(Value::Bool(false)),
_ => Err(DscError::Parser(t!("functions.invalidArguments").to_string())),
}
} else if let Some(num) = args[0].as_i64() {
Ok(Value::Bool(num != 0))
} else {
Err(DscError::Parser(t!("functions.invalidArguments").to_string()))
}
}
}

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

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

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

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

#[test]
fn number_0() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[bool(0)]", &Context::new()).unwrap();
assert_eq!(result, false);
}
}
52 changes: 52 additions & 0 deletions dsc_lib/src/functions/false.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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 False {}

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

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

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

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

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

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

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

#[test]
fn false_function() {
let mut parser = Statement::new().unwrap();
let result = parser.parse_and_execute("[false()]", &Context::new()).unwrap();
assert_eq!(result, false);
}
}
12 changes: 12 additions & 0 deletions dsc_lib/src/functions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,32 @@ use serde_json::Value;
use std::fmt::Display;

pub mod add;
pub mod and;
pub mod base64;
pub mod bool;
pub mod concat;
pub mod create_array;
pub mod div;
pub mod envvar;
pub mod equals;
pub mod r#if;
pub mod r#false;
pub mod format;
pub mod int;
pub mod max;
pub mod min;
pub mod mod_function;
pub mod mul;
pub mod not;
pub mod or;
pub mod parameters;
pub mod path;
pub mod reference;
pub mod resource_id;
pub mod secret;
pub mod sub;
pub mod system_root;
pub mod r#true;
pub mod variables;

/// The kind of argument that a function accepts.
Expand Down Expand Up @@ -77,26 +83,32 @@ impl FunctionDispatcher {
pub fn new() -> Self {
let mut functions: HashMap<String, Box<dyn Function>> = HashMap::new();
functions.insert("add".to_string(), Box::new(add::Add{}));
functions.insert("and".to_string(), Box::new(and::And{}));
functions.insert("base64".to_string(), Box::new(base64::Base64{}));
functions.insert("bool".to_string(), Box::new(bool::Bool{}));
functions.insert("concat".to_string(), Box::new(concat::Concat{}));
functions.insert("createArray".to_string(), Box::new(create_array::CreateArray{}));
functions.insert("div".to_string(), Box::new(div::Div{}));
functions.insert("envvar".to_string(), Box::new(envvar::Envvar{}));
functions.insert("equals".to_string(), Box::new(equals::Equals{}));
functions.insert("false".to_string(), Box::new(r#false::False{}));
functions.insert("if".to_string(), Box::new(r#if::If{}));
functions.insert("format".to_string(), Box::new(format::Format{}));
functions.insert("int".to_string(), Box::new(int::Int{}));
functions.insert("max".to_string(), Box::new(max::Max{}));
functions.insert("min".to_string(), Box::new(min::Min{}));
functions.insert("mod".to_string(), Box::new(mod_function::Mod{}));
functions.insert("mul".to_string(), Box::new(mul::Mul{}));
functions.insert("not".to_string(), Box::new(not::Not{}));
functions.insert("or".to_string(), Box::new(or::Or{}));
functions.insert("parameters".to_string(), Box::new(parameters::Parameters{}));
functions.insert("path".to_string(), Box::new(path::Path{}));
functions.insert("reference".to_string(), Box::new(reference::Reference{}));
functions.insert("resourceId".to_string(), Box::new(resource_id::ResourceId{}));
functions.insert("secret".to_string(), Box::new(secret::Secret{}));
functions.insert("sub".to_string(), Box::new(sub::Sub{}));
functions.insert("systemRoot".to_string(), Box::new(system_root::SystemRoot{}));
functions.insert("true".to_string(), Box::new(r#true::True{}));
functions.insert("variables".to_string(), Box::new(variables::Variables{}));
Self {
functions,
Expand Down
Loading
Loading