Skip to content

Commit e9cba87

Browse files
committed
Add tryIndexFromEnd() function
1 parent ff9a762 commit e9cba87

File tree

5 files changed

+495
-0
lines changed

5 files changed

+495
-0
lines changed
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
---
2+
description: Reference for the 'tryIndexFromEnd' DSC configuration document function
3+
ms.date: 01/29/2025
4+
ms.topic: reference
5+
title: tryIndexFromEnd
6+
---
7+
8+
## Synopsis
9+
10+
Safely retrieves a value from an array by counting backward from the end without
11+
throwing an error if the index is out of range.
12+
13+
## Syntax
14+
15+
```Syntax
16+
tryIndexFromEnd(sourceArray, reverseIndex)
17+
```
18+
19+
## Description
20+
21+
The `tryIndexFromEnd()` function provides a safe way to access array elements by
22+
counting backward from the end using a one-based index. Unlike standard array
23+
indexing that might fail with out-of-bounds errors, this function returns `null`
24+
when the index is invalid or out of range.
25+
26+
This is particularly useful when working with dynamic arrays where the length
27+
isn't known in advance, or when implementing fallback logic that needs to handle
28+
missing data gracefully. The function uses a one-based index, meaning `1`
29+
refers to the last element, `2` to the second-to-last, and so on.
30+
31+
The function returns `null` in the following cases:
32+
33+
- The reverse index is greater than the array length
34+
- The reverse index is zero or negative
35+
- The array is empty
36+
37+
## Examples
38+
39+
### Example 1 - Access recent deployment history safely
40+
41+
Use `tryIndexFromEnd()` to access recent deployment records when you're not
42+
certain how many deployments have occurred. This is useful for rollback
43+
scenarios where you want to retrieve the previous deployment without causing
44+
errors if the history is empty or shorter than expected. This example uses
45+
[`createArray()`][05] to build the deployment history and [`last()`][00] to get
46+
the current deployment.
47+
48+
```yaml
49+
# tryIndexFromEnd.example.1.dsc.config.yaml
50+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
51+
resources:
52+
- name: Deployment Rollback
53+
type: Microsoft.DSC.Debug/Echo
54+
properties:
55+
output:
56+
currentDeployment: "[last(createArray('v1.0.0', 'v1.1.0', 'v1.2.0'))]"
57+
previousDeployment: "[tryIndexFromEnd(createArray('v1.0.0', 'v1.1.0', 'v1.2.0'), 2)]"
58+
fallbackDeployment: "[tryIndexFromEnd(createArray('v1.0.0', 'v1.1.0', 'v1.2.0'), 10)]"
59+
```
60+
61+
```bash
62+
dsc config get --file tryIndexFromEnd.example.1.dsc.config.yaml
63+
```
64+
65+
```yaml
66+
results:
67+
- name: Deployment Rollback
68+
type: Microsoft.DSC.Debug/Echo
69+
result:
70+
actualState:
71+
output:
72+
currentDeployment: v1.2.0
73+
previousDeployment: v1.1.0
74+
fallbackDeployment: null
75+
messages: []
76+
hadErrors: false
77+
```
78+
79+
The function returns `v1.1.0` for the second-to-last deployment, and `null` for
80+
the non-existent 10th-from-last deployment, allowing your configuration to
81+
handle missing data gracefully.
82+
83+
### Example 2 - Select backup retention with safe defaults
84+
85+
Use `tryIndexFromEnd()` to implement flexible backup retention policies that
86+
adapt to available backups without failing when fewer backups exist than
87+
expected. This example retrieves the third-most-recent backup if available. This
88+
example uses [`createArray()`][05] to build the backup timestamps.
89+
90+
```yaml
91+
# tryIndexFromEnd.example.2.dsc.config.yaml
92+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
93+
resources:
94+
- name: Backup Retention
95+
type: Microsoft.DSC.Debug/Echo
96+
properties:
97+
output:
98+
backups: "[createArray(20250101, 20250108, 20250115, 20250122, 20250129)]"
99+
retainAfter: "[tryIndexFromEnd(createArray(20250101, 20250108, 20250115, 20250122, 20250129), 3)]"
100+
description: "Retain backups newer than the third-most-recent"
101+
```
102+
103+
```bash
104+
dsc config get --file tryIndexFromEnd.example.2.dsc.config.yaml
105+
```
106+
107+
```yaml
108+
results:
109+
- name: Backup Retention
110+
type: Microsoft.DSC.Debug/Echo
111+
result:
112+
actualState:
113+
output:
114+
backups:
115+
- 20250101
116+
- 20250108
117+
- 20250115
118+
- 20250122
119+
- 20250129
120+
retainAfter: 20250115
121+
description: Retain backups newer than the third-most-recent
122+
messages: []
123+
hadErrors: false
124+
```
125+
126+
The function safely returns `20250115` (the third-from-last backup), allowing
127+
you to implement a retention policy that keeps the three most recent backups.
128+
129+
### Example 3 - Parse log levels from configuration arrays
130+
131+
Use `tryIndexFromEnd()` to access configuration values from arrays of varying
132+
lengths. This is useful when configuration arrays might have different numbers
133+
of elements across environments. This example uses [`createArray()`][05] to
134+
build the log level arrays.
135+
136+
```yaml
137+
# tryIndexFromEnd.example.3.dsc.config.yaml
138+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
139+
resources:
140+
- name: Log Configuration
141+
type: Microsoft.DSC.Debug/Echo
142+
properties:
143+
output:
144+
productionLevels: "[createArray('ERROR', 'WARN', 'INFO')]"
145+
devLevels: "[createArray('ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE')]"
146+
prodThirdLevel: "[tryIndexFromEnd(createArray('ERROR', 'WARN', 'INFO'), 3)]"
147+
devThirdLevel: "[tryIndexFromEnd(createArray('ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE'), 3)]"
148+
prodFifthLevel: "[tryIndexFromEnd(createArray('ERROR', 'WARN', 'INFO'), 5)]"
149+
```
150+
151+
```bash
152+
dsc config get --file tryIndexFromEnd.example.3.dsc.config.yaml
153+
```
154+
155+
```yaml
156+
results:
157+
- name: Log Configuration
158+
type: Microsoft.DSC.Debug/Echo
159+
result:
160+
actualState:
161+
output:
162+
productionLevels:
163+
- ERROR
164+
- WARN
165+
- INFO
166+
devLevels:
167+
- ERROR
168+
- WARN
169+
- INFO
170+
- DEBUG
171+
- TRACE
172+
prodThirdLevel: ERROR
173+
devThirdLevel: INFO
174+
prodFifthLevel: null
175+
messages: []
176+
hadErrors: false
177+
```
178+
179+
The function safely handles arrays of different lengths, returning the
180+
appropriate log level or `null` without throwing errors.
181+
182+
### Example 4 - Access region-specific configuration with fallback
183+
184+
Use `tryIndexFromEnd()` with [`coalesce()`][02] to implement fallback logic when
185+
accessing configuration values from arrays that might have different lengths
186+
across regions. This example shows how to safely access regional endpoints with
187+
a default fallback. This example uses [`createArray()`][05] to build the
188+
regional endpoint arrays.
189+
190+
```yaml
191+
# tryIndexFromEnd.example.4.dsc.config.yaml
192+
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
193+
resources:
194+
- name: Regional Endpoints
195+
type: Microsoft.DSC.Debug/Echo
196+
properties:
197+
output:
198+
primaryRegion: "[createArray('us-east-1', 'us-west-2', 'eu-west-1')]"
199+
secondaryRegion: "[createArray('us-west-1')]"
200+
preferredPrimary: "[coalesce(tryIndexFromEnd(createArray('us-east-1', 'us-west-2', 'eu-west-1'), 2), 'us-east-1')]"
201+
preferredSecondary: "[coalesce(tryIndexFromEnd(createArray('us-west-1'), 2), 'us-west-1')]"
202+
```
203+
204+
```bash
205+
dsc config get --file tryIndexFromEnd.example.4.dsc.config.yaml
206+
```
207+
208+
```yaml
209+
results:
210+
- name: Regional Endpoints
211+
type: Microsoft.DSC.Debug/Echo
212+
result:
213+
actualState:
214+
output:
215+
primaryRegion:
216+
- us-east-1
217+
- us-west-2
218+
- eu-west-1
219+
secondaryRegion:
220+
- us-west-1
221+
preferredPrimary: us-west-2
222+
preferredSecondary: us-west-1
223+
messages: []
224+
hadErrors: false
225+
```
226+
227+
By combining `tryIndexFromEnd()` with `coalesce()`, you get robust fallback
228+
behavior: `preferredPrimary` returns `us-west-2` (the second-to-last region),
229+
while `preferredSecondary` falls back to the default `us-west-1` when the
230+
second-to-last element doesn't exist.
231+
232+
## Parameters
233+
234+
### sourceArray
235+
236+
The array to retrieve the element from by counting backward from the end.
237+
Required.
238+
239+
```yaml
240+
Type: array
241+
Required: true
242+
Position: 1
243+
```
244+
245+
### reverseIndex
246+
247+
The one-based index from the end of the array. Must be a positive integer where
248+
`1` refers to the last element, `2` to the second-to-last, and so on. Required.
249+
250+
```yaml
251+
Type: integer
252+
Required: true
253+
Position: 2
254+
Minimum: 1
255+
```
256+
257+
## Output
258+
259+
Returns the array element at the specified reverse index if the index is valid
260+
(within array bounds). Returns `null` if the index is out of range, zero,
261+
negative, or if the array is empty.
262+
263+
The return type matches the type of the element in the array.
264+
265+
```yaml
266+
Type: any | null
267+
```
268+
269+
## Errors
270+
271+
The function returns an error in the following cases:
272+
273+
- **Invalid source type**: The first argument is not an array
274+
- **Invalid index type**: The second argument is not an integer
275+
276+
## Related functions
277+
278+
- [`last()`][00] - Returns the last element of an array (throws error if empty)
279+
- [`first()`][01] - Returns the first element of an array or character of a string
280+
- [`coalesce()`][02] - Returns the first non-null value from a list
281+
- [`equals()`][03] - Compares two values for equality
282+
- [`not()`][04] - Inverts a boolean value
283+
- [`createArray()`][05] - Creates an array from provided values
284+
285+
<!-- Link reference definitions -->
286+
[00]: ./last.md
287+
[01]: ./first.md
288+
[02]: ./coalesce.md
289+
[03]: ./equals.md
290+
[04]: ./not.md
291+
[05]: ./createArray.md

dsc/tests/dsc_functions.tests.ps1

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,40 @@ Describe 'tests for function expressions' {
956956
}
957957
}
958958

959+
It 'tryIndexFromEnd() function works for: <expression>' -TestCases @(
960+
@{ expression = "[tryIndexFromEnd(createArray('a', 'b', 'c'), 1)]"; expected = 'c' }
961+
@{ expression = "[tryIndexFromEnd(createArray('a', 'b', 'c'), 2)]"; expected = 'b' }
962+
@{ expression = "[tryIndexFromEnd(createArray('a', 'b', 'c'), 3)]"; expected = 'a' }
963+
@{ expression = "[tryIndexFromEnd(createArray('a', 'b', 'c'), 4)]"; expected = $null }
964+
@{ expression = "[tryIndexFromEnd(createArray('a', 'b', 'c'), 0)]"; expected = $null }
965+
@{ expression = "[tryIndexFromEnd(createArray('a', 'b', 'c'), -1)]"; expected = $null }
966+
@{ expression = "[tryIndexFromEnd(createArray('only'), 1)]"; expected = 'only' }
967+
@{ expression = "[tryIndexFromEnd(createArray(10, 20, 30, 40), 2)]"; expected = 30 }
968+
@{ expression = "[tryIndexFromEnd(createArray(createObject('k', 'v1'), createObject('k', 'v2')), 1)]"; expected = [pscustomobject]@{ k = 'v2' } }
969+
@{ expression = "[tryIndexFromEnd(createArray(createArray(1, 2), createArray(3, 4)), 1)]"; expected = @(3, 4) }
970+
@{ expression = "[tryIndexFromEnd(createArray(), 1)]"; expected = $null }
971+
@{ expression = "[tryIndexFromEnd(createArray('x', 'y'), 1000)]"; expected = $null }
972+
) {
973+
param($expression, $expected)
974+
975+
$config_yaml = @"
976+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
977+
resources:
978+
- name: Echo
979+
type: Microsoft.DSC.Debug/Echo
980+
properties:
981+
output: "$expression"
982+
"@
983+
$out = $config_yaml | dsc config get -f - | ConvertFrom-Json
984+
if ($expected -is [pscustomobject]) {
985+
($out.results[0].result.actualState.output | Out-String) | Should -BeExactly ($expected | Out-String)
986+
} elseif ($expected -is [array]) {
987+
($out.results[0].result.actualState.output | ConvertTo-Json -Compress) | Should -BeExactly ($expected | ConvertTo-Json -Compress)
988+
} else {
989+
$out.results[0].result.actualState.output | Should -BeExactly $expected
990+
}
991+
}
992+
959993
It 'uriComponent function works for: <testInput>' -TestCases @(
960994
@{ testInput = 'hello world' }
961995
@{ testInput = 'hello@example.com' }

lib/dsc-lib/locales/en-us.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,12 @@ invoked = "tryGet function"
547547
invalidKeyType = "Invalid key type, must be a string"
548548
invalidIndexType = "Invalid index type, must be an integer"
549549

550+
[functions.tryIndexFromEnd]
551+
description = "Retrieves a value from an array by counting backward from the end. Returns null if the index is out of range."
552+
invoked = "tryIndexFromEnd function"
553+
invalidSourceType = "Invalid source type, must be an array"
554+
invalidIndexType = "Invalid index type, must be an integer"
555+
550556
[functions.union]
551557
description = "Returns a single array or object with all elements from the parameters"
552558
invoked = "union function"

lib/dsc-lib/src/functions/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ pub mod to_upper;
7171
pub mod trim;
7272
pub mod r#true;
7373
pub mod try_get;
74+
pub mod try_index_from_end;
7475
pub mod union;
7576
pub mod unique_string;
7677
pub mod uri;
@@ -200,6 +201,7 @@ impl FunctionDispatcher {
200201
Box::new(trim::Trim{}),
201202
Box::new(r#true::True{}),
202203
Box::new(try_get::TryGet{}),
204+
Box::new(try_index_from_end::TryIndexFromEnd{}),
203205
Box::new(union::Union{}),
204206
Box::new(unique_string::UniqueString{}),
205207
Box::new(uri::Uri{}),

0 commit comments

Comments
 (0)