Skip to content

Commit 46aff4a

Browse files
mkruegerCopilot
andcommitted
Merge main into JSON decimal conversion fix
Update PR #236 with the latest filter index validation changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2 parents a6e5467 + fa2b09c commit 46aff4a

6 files changed

Lines changed: 48 additions & 2 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Fixes
66

77
- Use consistent `double` conversion for JSON values in decimal arithmetic and comparisons, including numeric strings read through JSON paths.
8+
- Reject oversized `filter` array indexes instead of silently selecting element zero.
89
- Detect local Cosmos DB emulator connections by the parsed HTTP(S) endpoint host, preventing misleading remote URLs or unrelated connection-string fields from automatically disabling TLS certificate validation.
910

1011
## 1.1.271-preview — 2026-10-02

‎CosmosDBShell.Tests/CommandTests/FilterCommandTests.cs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,23 @@ namespace CosmosShell.Tests.CommandTests;
1111

1212
public class FilterCommandTests
1313
{
14+
[Theory]
15+
[InlineData(".[2147483648]")]
16+
[InlineData(".[2147483648]?")]
17+
[InlineData(".[999999999999999999999999999999]")]
18+
public async Task ExecuteAsync_RejectsOversizedIndex_WithoutChangingInput(string expression)
19+
{
20+
using var shell = ShellInterpreter.CreateInstance();
21+
var input = new ShellJson(JsonSerializer.SerializeToElement(new[] { 10, 20 }));
22+
var state = new CommandState { Result = input };
23+
var command = new FilterCommand { ExpressionText = expression };
24+
25+
await Assert.ThrowsAsync<CommandException>(() =>
26+
command.ExecuteAsync(shell, state, string.Empty, CancellationToken.None));
27+
28+
Assert.Same(input, state.Result);
29+
}
30+
1431
[Fact]
1532
public async Task ExecuteAsync_AppliesPathExpression_AndPreservesStructuredResult()
1633
{

‎CosmosDBShell.Tests/Parser/FilterPathExpressionTests.cs‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,28 @@ public async Task Index_OutOfRange_ReturnsNull()
7070
Assert.Equal(JsonValueKind.Null, Assert.IsType<ShellJson>(result).Value.ValueKind);
7171
}
7272

73+
[Theory]
74+
[InlineData(".[2147483648]")]
75+
[InlineData(".items[2147483648]?")]
76+
[InlineData(".[999999999999999999999999999999]")]
77+
public void Index_Overflow_ReportsErrorAtIndex(string input)
78+
{
79+
var lexer = new Lexer(input);
80+
var expression = new ExpressionParser(lexer).ParseFilterExpression();
81+
82+
Assert.IsType<ErrorExpression>(expression);
83+
Assert.True(lexer.Errors.HasErrors);
84+
var error = Assert.Single(lexer.Errors);
85+
Assert.Equal(input.IndexOf('[') + 1, error.Start);
86+
}
87+
88+
[Fact]
89+
public async Task Index_MaximumInt32_ReturnsNull()
90+
{
91+
var result = await EvalAsync(".[2147483647]", new[] { 10, 20, 30 });
92+
Assert.Equal(JsonValueKind.Null, Assert.IsType<ShellJson>(result).Value.ValueKind);
93+
}
94+
7395
[Fact]
7496
public async Task Index_OnNonArrayWithoutOptional_Throws()
7597
=> await Assert.ThrowsAsync<CommandException>(() => EvalAsync(".[0]", new { a = 1 }));

‎CosmosDBShell/Azure.Data.Cosmos.Shell.Parser/ExpressionParser.cs‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1722,7 +1722,12 @@ private Expression ParseFilterPathExpression(Token firstToken)
17221722
}
17231723

17241724
var indexToken = this.Consume(TokenType.Number, MessageService.GetString("expression_error_expected_array_index"));
1725-
int index = int.TryParse(indexToken.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedIndex) ? parsedIndex : 0;
1725+
if (!int.TryParse(indexToken.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var index))
1726+
{
1727+
this.ReportError(MessageService.GetArgsString("expression_error_invalid_number", "value", indexToken.Value), indexToken);
1728+
return new ErrorExpression(indexToken.Start, indexToken.Length);
1729+
}
1730+
17261731
var indexedCloseBracket = this.Consume(TokenType.CloseBracket, MessageService.GetString("expression_error_expected_close_bracket"));
17271732
var indexQuestionToken = this.TryConsumeQuestion();
17281733
end = indexedCloseBracket.Start + indexedCloseBracket.Length;

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ A terminal-native shell for Azure Cosmos DB — navigate databases like a filesy
2929
- Tail the change feed of a container with `watch` (alias `tail`)
3030
- Database and container management commands prefer Azure Resource Manager when connected with Entra ID, with data-plane fallback for key, emulator, and static-token connections
3131
- Pipelines and scripting with variables, loops, functions
32-
- Transform piped JSON output with `filter` using jq-inspired expressions (field access, indexing, `map`, `length`, pipelines)
32+
- Transform piped JSON output with `filter` using jq-inspired expressions (field access, indexing, `map`, `length`, pipelines). Indexes larger than `2147483647` are rejected; valid indexes beyond the array length return `null`.
3333
- Edit local files in your external editor with `edit`, and customize REPL colors with `theme` (`list`, `show`, `use`, `load`, `validate`, `save`, `edit`; built-in default/light/dark/monochrome)
3434
- Multi-line input at the prompt — automatic continuation for unclosed blocks/strings, plus explicit `\` line continuation ([docs](docs/navigation.md#multi-line-input))
3535
- MCP server for AI/tool integration

‎docs/filter-v1-spec.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ filter <expression>
2828

2929
- `expression` is required.
3030
- The expression is usually quoted at the shell level, for example: `filter '.items[0]'`.
31+
- Array indexes must be integer literals between `0` and `2147483647`. Larger indexes are rejected, including optional paths (`?`); valid indexes beyond the array length return `null`.
3132
- Input comes from the current pipeline value.
3233
- Output is written back into the shell's structured command result.
3334

0 commit comments

Comments
 (0)