diff --git a/src/Core/Parsers/RequestParser.cs b/src/Core/Parsers/RequestParser.cs index 081018e820..223cb3a38f 100644 --- a/src/Core/Parsers/RequestParser.cs +++ b/src/Core/Parsers/RequestParser.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Net; +using System.Web; using Azure.DataApiBuilder.Core.Models; using Azure.DataApiBuilder.Core.Services; using Azure.DataApiBuilder.Service.Exceptions; @@ -321,6 +322,12 @@ private static bool IsNull(string value) return null; } + // Encode the parameterName to ensure it matches the encoding in the query string if the $ sign is URL encoded. + if (!queryString.Contains(parameterName, StringComparison.OrdinalIgnoreCase)) + { + parameterName = HttpUtility.UrlEncode(parameterName); + } + // Split on '&' which are parameter separators in properly URL-encoded query strings. // Any '&' characters within parameter values will be encoded as %26. foreach (string param in queryString.TrimStart('?').Split('&')) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 203de98aef..71ef6ed6cf 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -4224,6 +4224,47 @@ public async Task OpenApi_InteractiveSwaggerUI( } } + /// + /// End to end test that validates that REST requests with OData query + /// options $filter and $orderby succeed to ensure no regression can occur. + /// + [DataTestMethod] + [DataRow("/api/Book?$orderby=id desc&$filter=publisher_id eq 1234", DisplayName = "REST URL without encoded characters")] + [DataRow("/api/Book?%24orderby=id%20desc&%24filter=publisher_id%20eq%201234", DisplayName = "REST URL with encoded characters")] + [TestCategory(TestCategory.MSSQL)] + public async Task TestForRestRequestsWithFilterAndOrderbyParameters(string restUri) + { + // The configuration file is constructed by merging hard-coded JSON strings to simulate the scenario where users manually edit the + // configuration file (instead of using CLI). + string configJson = TestHelper.AddPropertiesToJson(TestHelper.BASE_CONFIG, BOOK_ENTITY_JSON); + Assert.IsTrue(RuntimeConfigLoader.TryParseConfig( + configJson, + out RuntimeConfig deserializedConfig, + replacementSettings: new(), + connectionString: GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL))); + string configFileName = "custom-config.json"; + File.WriteAllText(configFileName, deserializedConfig.ToJson()); + string[] args = new[] + { + $"--ConfigFileName={configFileName}" + }; + + using (TestServer server = new(Program.CreateWebHostBuilder(args))) + using (HttpClient client = server.CreateClient()) + { + // Act + using HttpRequestMessage restRequest = new(HttpMethod.Get, restUri); + using HttpResponseMessage restResponse = await client.SendAsync(restRequest); + + // Assert - Verify REST response + Assert.AreEqual(HttpStatusCode.OK, restResponse.StatusCode, "REST request to auto-generated entity should succeed"); + + string restResponseBody = await restResponse.Content.ReadAsStringAsync(); + Assert.IsTrue(!string.IsNullOrEmpty(restResponseBody), "REST response should contain data"); + Assert.IsTrue(restResponseBody.Contains("\"publisher_id\":1234")); + } + } + /// /// Test different loglevel values that are avaliable by deserializing RuntimeConfig with specified LogLevel /// and checks if value exists properly inside the deserialized RuntimeConfig.