Skip to content

Commit 0052a76

Browse files
committed
Add string function
1 parent 18fd542 commit 0052a76

File tree

5 files changed

+372
-0
lines changed

5 files changed

+372
-0
lines changed
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
---
2+
description: Reference for the 'string' DSC configuration document function
3+
ms.date: 08/09/2025
4+
ms.topic: reference
5+
title: string
6+
---
7+
8+
# string
9+
10+
## Synopsis
11+
12+
Converts a value to a string representation.
13+
14+
## Syntax
15+
16+
```Syntax
17+
string(<value>)
18+
```
19+
20+
## Description
21+
22+
The `string()` function converts a value of any type to its string
23+
representation. This is useful for formatting output, concatenating values, or
24+
ensuring consistent data types. Arrays and objects are converted to JSON
25+
strings, while primitive types are converted to their standard string
26+
representations.
27+
28+
## Examples
29+
30+
### Example 1 - Convert integers to strings
31+
32+
The following example shows how to convert numbers to strings for display
33+
purposes.
34+
35+
```yaml
36+
# string.example.1.dsc.config.yaml
37+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
38+
parameters:
39+
serverCount:
40+
type: int
41+
defaultValue: 42
42+
memorySize:
43+
type: int
44+
defaultValue: 16
45+
resources:
46+
- name: Convert numbers
47+
type: Microsoft.DSC.Debug/Echo
48+
properties:
49+
output:
50+
serverCountString: "[string(parameters('serverCount'))]"
51+
memorySizeString: "[string(parameters('memorySize'))]"
52+
literalNumber: "[string(123)]"
53+
```
54+
55+
```bash
56+
dsc config get --file string.example.1.dsc.config.yaml
57+
```
58+
59+
```yaml
60+
results:
61+
- name: Convert numbers
62+
type: Microsoft.DSC.Debug/Echo
63+
result:
64+
actualState:
65+
output:
66+
serverCountString: '42'
67+
memorySizeString: '16'
68+
literalNumber: '123'
69+
messages: []
70+
hadErrors: false
71+
```
72+
73+
### Example 2 - Convert arrays and objects to JSON strings
74+
75+
The following example shows how arrays and objects are converted to JSON
76+
strings.
77+
78+
```yaml
79+
# string.example.2.dsc.config.yaml
80+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
81+
parameters:
82+
serverList:
83+
type: array
84+
defaultValue:
85+
- web01
86+
- web02
87+
- db01
88+
config:
89+
type: object
90+
defaultValue:
91+
timeout: 30
92+
retries: 3
93+
enabled: true
94+
resources:
95+
- name: Convert collections
96+
type: Microsoft.DSC.Debug/Echo
97+
properties:
98+
output:
99+
serversJson: "[string(parameters('serverList'))]"
100+
configJson: "[string(parameters('config'))]"
101+
arrayLiteral: "[string(createArray('a', 'b'))]"
102+
```
103+
104+
```bash
105+
dsc config get --file string.example.3.dsc.config.yaml
106+
```
107+
108+
```yaml
109+
results:
110+
- metadata:
111+
Microsoft.DSC:
112+
duration: PT0.1452881S
113+
name: Convert collections
114+
type: Microsoft.DSC.Debug/Echo
115+
result:
116+
actualState:
117+
output:
118+
serversJson: '["web01","web02","db01"]'
119+
configJson: '{"timeout":30,"retries":3,"enabled":true}'
120+
arrayLiteral: '["a","b"]'
121+
messages: []
122+
hadErrors: false
123+
```
124+
125+
### Example 3 - Building formatted messages
126+
127+
The following example shows a practical use case for building formatted
128+
messages using string conversion.
129+
130+
```yaml
131+
# string.example.3.dsc.config.yaml
132+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
133+
parameters:
134+
deploymentId:
135+
type: int
136+
defaultValue: 12345
137+
isProduction:
138+
type: bool
139+
defaultValue: false
140+
serverCount:
141+
type: int
142+
defaultValue: 3
143+
resources:
144+
- name: Build status message
145+
type: Microsoft.DSC.Debug/Echo
146+
properties:
147+
output:
148+
deploymentInfo: "[concat('Deployment ', string(parameters('deploymentId')), ' running in ', if(parameters('isProduction'), 'production', 'development'), ' mode')]"
149+
serverMessage: "[concat('Managing ', string(parameters('serverCount')), ' server(s)')]"
150+
statusFlag: "[concat('Production: ', string(parameters('isProduction')))]"
151+
```
152+
153+
```bash
154+
dsc config get --file string.example.4.dsc.config.yaml
155+
```
156+
157+
```yaml
158+
results:
159+
- name: Build status message
160+
type: Microsoft.DSC.Debug/Echo
161+
result:
162+
actualState:
163+
output:
164+
deploymentInfo: Deployment 12345 running in development mode
165+
serverMessage: Managing 3 server(s)
166+
statusFlag: 'Production: false'
167+
messages: []
168+
hadErrors: false
169+
```
170+
171+
### Example 4 - String conversion for logging
172+
173+
The following example demonstrates converting various data types for logging
174+
purposes.
175+
176+
```yaml
177+
# string.example.4.dsc.config.yaml
178+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
179+
parameters:
180+
timestamp:
181+
type: int
182+
defaultValue: 1691596800
183+
errorCode:
184+
type: int
185+
defaultValue: 404
186+
metadata:
187+
type: object
188+
defaultValue:
189+
source: "api"
190+
level: "error"
191+
resources:
192+
- name: Generate log entry
193+
type: Microsoft.DSC.Debug/Echo
194+
properties:
195+
output:
196+
logEntry: "[concat('[', string(parameters('timestamp')), '] ERROR ', string(parameters('errorCode')), ': ', string(parameters('metadata')))]"
197+
```
198+
199+
```bash
200+
dsc config get --file string.example.5.dsc.config.yaml
201+
```
202+
203+
```yaml
204+
results:
205+
- name: Generate log entry
206+
type: Microsoft.DSC.Debug/Echo
207+
result:
208+
actualState:
209+
output:
210+
logEntry: '[1691596800] ERROR 404: {"level":"error","source":"api"}'
211+
messages: []
212+
hadErrors: false
213+
```
214+
215+
## Parameters
216+
217+
### value
218+
219+
The value to convert to a string.
220+
221+
```yaml
222+
Type: [string, number, bool, null, array, object]
223+
Required: true
224+
```
225+
226+
The `string()` function accepts exactly one input value of any type. The
227+
conversion behavior depends on the input type:
228+
229+
- **String**: Returns the string unchanged
230+
- **Number**: Converts to decimal string representation
231+
- **Boolean**: Converts to "true" or "false"
232+
- **Null**: Converts to "null"
233+
- **Array**: Converts to JSON array string
234+
- **Object**: Converts to JSON object string
235+
236+
## Output
237+
238+
The `string()` function returns the string representation of the input value.
239+
240+
```yaml
241+
Type: string
242+
```
243+
244+
<!-- Link reference definitions -->

dsc/tests/dsc_functions.tests.ps1

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,4 +320,32 @@ Describe 'tests for function expressions' {
320320
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw)
321321
$out.results[0].result.actualState.output | Should -BeExactly $expected
322322
}
323+
324+
It 'string function works for: <expression>' -TestCases @(
325+
@{ expression = "[string('hello')]"; expected = 'hello' }
326+
@{ expression = "[string(123)]"; expected = '123' }
327+
@{ expression = "[string(true)]"; expected = 'true' }
328+
@{ expression = "[string(null)]"; expected = 'null' }
329+
@{ expression = "[string(createArray('a', 1))]"; expected = '[
330+
"a",
331+
1
332+
]' }
333+
@{ expression = "[string(createObject('a', 1))]"; expected = '{
334+
"a": 1
335+
}' }
336+
) {
337+
param($expression, $expected)
338+
339+
$config_yaml = @"
340+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
341+
resources:
342+
- name: Echo
343+
type: Microsoft.DSC.Debug/Echo
344+
properties:
345+
output: "$expression"
346+
"@
347+
$out = dsc -l trace config get -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json
348+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw)
349+
($out.results[0].result.actualState.output | Out-String) | Should -BeExactly ($expected | Out-String)
350+
}
323351
}

dsc_lib/locales/en-us.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,9 @@ secretNotFound = "Secret '%{name}' not found"
389389
description = "Checks if a string starts with a specific prefix"
390390
invoked = "startsWith function"
391391

392+
[functions.string]
393+
description = "Converts a value to a string"
394+
392395
[functions.sub]
393396
description = "Subtracts the second number from the first"
394397
invoked = "sub function"

dsc_lib/src/functions/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub mod reference;
4747
pub mod resource_id;
4848
pub mod secret;
4949
pub mod starts_with;
50+
pub mod string;
5051
pub mod sub;
5152
pub mod system_root;
5253
pub mod r#true;
@@ -152,6 +153,7 @@ impl FunctionDispatcher {
152153
Box::new(resource_id::ResourceId{}),
153154
Box::new(secret::Secret{}),
154155
Box::new(starts_with::StartsWith{}),
156+
Box::new(string::StringFn{}),
155157
Box::new(sub::Sub{}),
156158
Box::new(system_root::SystemRoot{}),
157159
Box::new(r#true::True{}),

dsc_lib/src/functions/string.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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::{FunctionArgKind, FunctionCategory, FunctionMetadata};
7+
use rust_i18n::t;
8+
use serde_json::Value;
9+
use super::Function;
10+
11+
#[derive(Debug, Default)]
12+
pub struct StringFn {}
13+
14+
impl Function for StringFn {
15+
fn get_metadata(&self) -> FunctionMetadata {
16+
FunctionMetadata {
17+
name: "string".to_string(),
18+
description: t!("functions.string.description").to_string(),
19+
category: FunctionCategory::String,
20+
min_args: 1,
21+
max_args: 1,
22+
accepted_arg_ordered_types: vec![vec![
23+
FunctionArgKind::String,
24+
FunctionArgKind::Number,
25+
FunctionArgKind::Boolean,
26+
FunctionArgKind::Null,
27+
FunctionArgKind::Array,
28+
FunctionArgKind::Object,
29+
]],
30+
remaining_arg_accepted_types: None,
31+
return_types: vec![FunctionArgKind::String],
32+
}
33+
}
34+
35+
fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
36+
let value = &args[0];
37+
let result = match value {
38+
Value::String(s) => s.clone(),
39+
Value::Number(n) => n.to_string(),
40+
Value::Bool(b) => b.to_string(),
41+
Value::Null => "null".to_string(),
42+
Value::Array(_) | Value::Object(_) => serde_json::to_string(value)?,
43+
};
44+
Ok(Value::String(result))
45+
}
46+
}
47+
48+
#[cfg(test)]
49+
mod tests {
50+
use crate::configure::context::Context;
51+
use crate::parser::Statement;
52+
use serde_json::json;
53+
54+
#[test]
55+
fn string_from_string() {
56+
let mut parser = Statement::new().unwrap();
57+
let result = parser.parse_and_execute("[string('hello')]", &Context::new()).unwrap();
58+
assert_eq!(result, "hello");
59+
}
60+
61+
#[test]
62+
fn string_from_number() {
63+
let mut parser = Statement::new().unwrap();
64+
let result = parser.parse_and_execute("[string(123)]", &Context::new()).unwrap();
65+
assert_eq!(result, "123");
66+
}
67+
68+
#[test]
69+
fn string_from_bool() {
70+
let mut parser = Statement::new().unwrap();
71+
let result = parser.parse_and_execute("[string(true)]", &Context::new()).unwrap();
72+
assert_eq!(result, "true");
73+
}
74+
75+
#[test]
76+
fn string_from_null() {
77+
let mut parser = Statement::new().unwrap();
78+
let result = parser.parse_and_execute("[string(null)]", &Context::new()).unwrap();
79+
assert_eq!(result, "null");
80+
}
81+
82+
#[test]
83+
fn string_from_array() {
84+
let mut parser = Statement::new().unwrap();
85+
let result = parser.parse_and_execute("[string(createArray('a', 1))]", &Context::new()).unwrap();
86+
assert_eq!(result, json!(["a", 1]).to_string());
87+
}
88+
89+
#[test]
90+
fn string_from_object() {
91+
let mut parser = Statement::new().unwrap();
92+
let result = parser.parse_and_execute("[string(createObject('a', 1))]", &Context::new()).unwrap();
93+
assert_eq!(result, json!({"a": 1}).to_string());
94+
}
95+
}

0 commit comments

Comments
 (0)