Skip to content

Commit 9c47c64

Browse files
Fix failure on REST requests with $filter and $orderby parameters (#3664)
## Why make this change? - Fix issue #3662 This issue is caused by a regression in PR #3080 The regression is that we start using the raw encoded URL and we assume that the `$` character is not encoded when using the `ExtractRawQueryParameter` function, this is half true, since depending on where the URL comes from it will encode it or it will treat it as a special character and leave it as it is. This means the function looks for the `$filter` or `$orderby` when the URL has the possibility to change them to `%24filter` or `%24orderby` which causes the failure. ## What is this change? This change fixes a bug that caused using `$filter` or `$orderby` in REST. This is done by encoding the `parameterName` so that it has the same format as the raw URL, it will only change it if it is unable to find the original `$` character in the raw query. ## How was this tested? - [ ] Integration Tests - [x] Unit Tests Added end to end test to ensure that REST requests that use `$filter` and `$orderby` succeed for both cases where a user uses the special character `$` or its URL encoded format `%24`. ## Sample Request(s) http://localhost:5000/api/Book?$orderby=id desc http://localhost:5000/api/Book?$filter=publisher_id 1234
1 parent 36a5fea commit 9c47c64

2 files changed

Lines changed: 48 additions & 0 deletions

File tree

src/Core/Parsers/RequestParser.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System.Net;
5+
using System.Web;
56
using Azure.DataApiBuilder.Core.Models;
67
using Azure.DataApiBuilder.Core.Services;
78
using Azure.DataApiBuilder.Service.Exceptions;
@@ -321,6 +322,12 @@ private static bool IsNull(string value)
321322
return null;
322323
}
323324

325+
// Encode the parameterName to ensure it matches the encoding in the query string if the $ sign is URL encoded.
326+
if (!queryString.Contains(parameterName, StringComparison.OrdinalIgnoreCase))
327+
{
328+
parameterName = HttpUtility.UrlEncode(parameterName);
329+
}
330+
324331
// Split on '&' which are parameter separators in properly URL-encoded query strings.
325332
// Any '&' characters within parameter values will be encoded as %26.
326333
foreach (string param in queryString.TrimStart('?').Split('&'))

src/Service.Tests/Configuration/ConfigurationTests.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4224,6 +4224,47 @@ public async Task OpenApi_InteractiveSwaggerUI(
42244224
}
42254225
}
42264226

4227+
/// <summary>
4228+
/// End to end test that validates that REST requests with OData query
4229+
/// options $filter and $orderby succeed to ensure no regression can occur.
4230+
/// </summary>
4231+
[DataTestMethod]
4232+
[DataRow("/api/Book?$orderby=id desc&$filter=publisher_id eq 1234", DisplayName = "REST URL without encoded characters")]
4233+
[DataRow("/api/Book?%24orderby=id%20desc&%24filter=publisher_id%20eq%201234", DisplayName = "REST URL with encoded characters")]
4234+
[TestCategory(TestCategory.MSSQL)]
4235+
public async Task TestForRestRequestsWithFilterAndOrderbyParameters(string restUri)
4236+
{
4237+
// The configuration file is constructed by merging hard-coded JSON strings to simulate the scenario where users manually edit the
4238+
// configuration file (instead of using CLI).
4239+
string configJson = TestHelper.AddPropertiesToJson(TestHelper.BASE_CONFIG, BOOK_ENTITY_JSON);
4240+
Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(
4241+
configJson,
4242+
out RuntimeConfig deserializedConfig,
4243+
replacementSettings: new(),
4244+
connectionString: GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL)));
4245+
string configFileName = "custom-config.json";
4246+
File.WriteAllText(configFileName, deserializedConfig.ToJson());
4247+
string[] args = new[]
4248+
{
4249+
$"--ConfigFileName={configFileName}"
4250+
};
4251+
4252+
using (TestServer server = new(Program.CreateWebHostBuilder(args)))
4253+
using (HttpClient client = server.CreateClient())
4254+
{
4255+
// Act
4256+
using HttpRequestMessage restRequest = new(HttpMethod.Get, restUri);
4257+
using HttpResponseMessage restResponse = await client.SendAsync(restRequest);
4258+
4259+
// Assert - Verify REST response
4260+
Assert.AreEqual(HttpStatusCode.OK, restResponse.StatusCode, "REST request to auto-generated entity should succeed");
4261+
4262+
string restResponseBody = await restResponse.Content.ReadAsStringAsync();
4263+
Assert.IsTrue(!string.IsNullOrEmpty(restResponseBody), "REST response should contain data");
4264+
Assert.IsTrue(restResponseBody.Contains("\"publisher_id\":1234"));
4265+
}
4266+
}
4267+
42274268
/// <summary>
42284269
/// Test different loglevel values that are avaliable by deserializing RuntimeConfig with specified LogLevel
42294270
/// and checks if value exists properly inside the deserialized RuntimeConfig.

0 commit comments

Comments
 (0)