Sanitize IDictionary query parameters to skip null values - #755
Sanitize IDictionary query parameters to skip null values#755Bart Koelman (bkoelman) wants to merge 2 commits into
Conversation
01bb318 to
2ba7a12
Compare
There was a problem hiding this comment.
Pull request overview
This PR updates RequestInformation URL template expansion to properly handle IDictionary query parameter values by dropping entries with null values, enabling “free-form” query parameters (e.g., JSON:API-style dictionaries) and aligning behavior with RFC 6570 undefined-value semantics.
Changes:
- Add
IDictionaryhandling toRequestInformationsanitization so dictionary entries withnullvalues are omitted duringStd.UriTemplateexpansion. - Add unit tests covering exploded dictionary query parameters, null skipping, empty-string inclusion, and
AddQueryParametersintegration. - Extend the test query-parameter model to include a
Querydictionary property.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/abstractions/RequestInformation.cs | Adds dictionary sanitization to drop null entries before URI template expansion. |
| tests/abstractions/RequestInformationTests.cs | Adds coverage for dictionary-based query parameter expansion and null-handling behavior. |
| foreach(DictionaryEntry entry in dict) | ||
| { | ||
| if(entry.Value is not null) | ||
| { | ||
| result[(string)entry.Key] = GetSanitizedValue(entry.Value).ToString()!; | ||
| } | ||
| } |
There was a problem hiding this comment.
According to the plan (section PR 2), this is beyond the scope of this change. The dictionary signature of the client code generated by kiota should be Dictionary<string, string>, which can't contain null or non-string keys, so taking this change would basically introduce dead code.
|
|
||
| // Assert | ||
| var url = requestInfo.URI.OriginalString; | ||
| Assert.DoesNotContain("include", url); |
There was a problem hiding this comment.
This is intentional; the include parameter must not appear, whether or not it ends with an = sign.
| // page[size] key is percent-encoded by RFC 6570 {?} operator | ||
| Assert.Contains("page", url); | ||
| Assert.Contains("10", url); |
There was a problem hiding this comment.
The goal of these tests is not to verify encoding/formatting, so if that part of the project ever changes, they should not break these tests.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
src/abstractions/RequestInformation.cs:134
- SanitizeDictionary casts
entry.Keytostring, which will throw anInvalidCastException(and with a fairly opaque message) if a consumer passes anIDictionarywith non-string keys. Also, forcing.ToString()here is inconsistent withExpandArray/scalar sanitization (which keep non-string primitives as objects) and can subtly change formatting (e.g., culture-sensitivedecimal/double). Consider validating keys and keeping sanitized values asobjectsoStd.UriTemplatecan handle formatting consistently.
private static Dictionary<string, string> SanitizeDictionary(IDictionary dict)
{
var result = new Dictionary<string, string>(dict.Count);
foreach(DictionaryEntry entry in dict)
{
if(entry.Value is not null)
{
result[(string)entry.Key] = GetSanitizedValue(entry.Value).ToString()!;
}
tests/abstractions/RequestInformationTests.cs:951
- The assertions for the
page[size]query parameter are very permissive (Contains("page")/Contains("10")) and don't actually verify the percent-encoding behavior described in the comment. This test can pass even if the key is not encoded correctly or if10appears elsewhere in the URL.
var url = requestInfo.URI.OriginalString;
Assert.Contains("include=author", url);
// page[size] key is percent-encoded by RFC 6570 {?} operator
Assert.Contains("page", url);
Assert.Contains("10", url);
Vincent Biret (baywet)
left a comment
There was a problem hiding this comment.
Thanks for the contribution!
| /// </summary> | ||
| private static Dictionary<string, string> SanitizeDictionary(IDictionary dict) | ||
| { | ||
| var result = new Dictionary<string, string>(dict.Count); |
There was a problem hiding this comment.
Can't we use Dictionary<string, object> so we can conform with the rest of the sanitization approach and we don't have to call ToString() below (which could potentially cause issues with number formatting etc... down the road)
There was a problem hiding this comment.
Pushed, but that doesn't make sense to me at all. It's something entirely different from what's in the plan:
I've always assumed that dictionary values must be constrained to strings, which is what they are in an HTTP query string. Are you suggesting we deviate from the plan and switch to object values in the generated APIs?
There was a problem hiding this comment.
sorry if I wasn't clear. I was only suggesting that we update the return type of the private method to align with the rest.
In terms of usage, the users should able to use dictionary<string, string | int | float |.... > (any primitive type) on their query parameters class if that makes sense?
… values Co-authored-by: Cursor <cursoragent@cursor.com>
…nary query parameters Update SanitizeDictionary return type from Dictionary<string, string> to Dictionary<string, object> and route dictionary entry values through GetSanitizedValues instead of calling .ToString()!. This avoids forcing string conversions on numbers, booleans, and dates in dictionary query parameters, allowing Std.UriTemplate to handle formatting natively according to RFC 6570. Co-authored-by: Cursor <cursoragent@cursor.com>
2ba7a12 to
e147dd9
Compare
| foreach(DictionaryEntry entry in dict) | ||
| { | ||
| if(entry.Value is not null) | ||
| { | ||
| result[entry.Key.ToString()!] = GetSanitizedValues(entry.Value); | ||
| } | ||
| } |
There was a problem hiding this comment.
This matches the local formatting style of this specific file. Adding spaces introduces inconsistency.
Contributes to microsoft/kiota#3800.