From 35d6f1a1bf84c406233570c1a80e95902d520368 Mon Sep 17 00:00:00 2001 From: MD Ashique Date: Fri, 29 Sep 2023 01:36:41 +0530 Subject: [PATCH] Fixed property casing issue (#154) * Fixed property casing issue Signed-off-by: MD Ashique * Update src/Microsoft.Azure.WebJobs.Extensions.Dapr/DaprExtensionConfigProvider.cs Co-authored-by: Shubham Sharma * Update src/Microsoft.Azure.WebJobs.Extensions.Dapr/Utils/DictionaryUtils.cs Co-authored-by: Shubham Sharma * renaming variable Signed-off-by: MD Ashique --------- Signed-off-by: MD Ashique Co-authored-by: Shubham Sharma --- .../OutputBinding/InvokeOutputBinding.cs | 2 +- .../DaprExtensionConfigProvider.cs | 32 +++--- .../Utils/DictionaryUtils.cs | 42 +++++++ .../UnitTests/Utils/DictionaryUtilsTests.cs | 104 ++++++++++++++++++ .../EnvironmentExtensionsTests.cs | 2 +- 5 files changed, 167 insertions(+), 15 deletions(-) create mode 100644 src/Microsoft.Azure.WebJobs.Extensions.Dapr/Utils/DictionaryUtils.cs create mode 100644 test/DaprExtensionTests/UnitTests/Utils/DictionaryUtilsTests.cs rename test/DaprExtensionTests/UnitTests/{Services => Utils}/EnvironmentExtensionsTests.cs (99%) diff --git a/samples/dotnet-isolated-azurefunction/OutputBinding/InvokeOutputBinding.cs b/samples/dotnet-isolated-azurefunction/OutputBinding/InvokeOutputBinding.cs index e7121cd5..ab4a76b9 100644 --- a/samples/dotnet-isolated-azurefunction/OutputBinding/InvokeOutputBinding.cs +++ b/samples/dotnet-isolated-azurefunction/OutputBinding/InvokeOutputBinding.cs @@ -22,7 +22,7 @@ public static class InvokeOutputBinding [Function("InvokeOutputBinding")] [DaprInvokeOutput(AppId = "{appId}", MethodName = "{methodName}", HttpVerb = "post")] public static async Task Run( - [HttpTrigger(AuthorizationLevel.Function, "get", Route = "invoke/{appId}/{methodName}")] HttpRequestData req, FunctionContext functionContext) + [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "invoke/{appId}/{methodName}")] HttpRequestData req, FunctionContext functionContext) { var log = functionContext.GetLogger("InvokeOutputBinding"); log.LogInformation("C# HTTP trigger function processed a request."); diff --git a/src/Microsoft.Azure.WebJobs.Extensions.Dapr/DaprExtensionConfigProvider.cs b/src/Microsoft.Azure.WebJobs.Extensions.Dapr/DaprExtensionConfigProvider.cs index bb9cd8fb..6aff8f44 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.Dapr/DaprExtensionConfigProvider.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.Dapr/DaprExtensionConfigProvider.cs @@ -147,7 +147,9 @@ static DaprPubSubEvent CreatePubSubEvent(JToken arg) static DaprPubSubEvent CreatePubSubEvent(JsonElement json) { - if (!json.TryGetProperty("payload", out JsonElement payload)) + var propertyBag = json.ToCaseInsensitiveDictionary(); + + if (!propertyBag.TryGetValue("payload", out JsonElement payload)) { throw new ArgumentException($"A '{nameof(json).ToLowerInvariant()}' parameter is required for outbound pub/sub operations."); } @@ -160,12 +162,12 @@ static DaprPubSubEvent CreatePubSubEvent(JsonElement json) DaprPubSubEvent event_ = new DaprPubSubEvent(payloadObject); - if (json.TryGetProperty("pubsubname", out JsonElement pubsubName)) + if (propertyBag.TryGetValue("pubsubname", out JsonElement pubsubName)) { event_.PubSubName = pubsubName.GetString(); } - if (json.TryGetProperty("topic", out JsonElement topic)) + if (propertyBag.TryGetValue("topic", out JsonElement topic)) { event_.Topic = topic.GetString(); } @@ -227,7 +229,8 @@ private static DaprBindingMessage GetBindingMessageFromValueKindString(JsonEleme private static DaprBindingMessage GetDaprBindingMessageFromValueKindObject(JsonElement jsonElement) { - if (!jsonElement.TryGetProperty("data", out JsonElement data)) + var propertyBag = jsonElement.ToCaseInsensitiveDictionary(); + if (!propertyBag.TryGetValue("data", out JsonElement data)) { throw new ArgumentException("A 'data' parameter is required for Dapr Binding operations.", nameof(jsonElement)); } @@ -240,17 +243,17 @@ private static DaprBindingMessage GetDaprBindingMessageFromValueKindObject(JsonE DaprBindingMessage message = new DaprBindingMessage(dataObj); - if (jsonElement.TryGetProperty("operation", out JsonElement operation)) + if (propertyBag.TryGetValue("operation", out JsonElement operation)) { message.Operation = JsonSerializer.Deserialize(operation); } - if (jsonElement.TryGetProperty("metadata", out JsonElement metadata)) + if (propertyBag.TryGetValue("metadata", out JsonElement metadata)) { message.Metadata = JsonSerializer.Deserialize>(metadata); } - if (jsonElement.TryGetProperty("bindingName", out JsonElement binding)) + if (propertyBag.TryGetValue("bindingname", out JsonElement binding)) { message.BindingName = JsonSerializer.Deserialize(binding); } @@ -275,14 +278,15 @@ internal static DaprStateRecord CreateSaveStateParameters(JToken value) internal static DaprStateRecord CreateSaveStateParameters(JsonElement parametersJson) { - if (!parametersJson.TryGetProperty("value", out JsonElement value)) + var propertyBag = parametersJson.ToCaseInsensitiveDictionary(); + if (!propertyBag.TryGetValue("value", out JsonElement value)) { throw new ArgumentException("A 'value' parameter is required for save-state operations.", nameof(parametersJson)); } var parameters = new DaprStateRecord(value); - if (parametersJson.TryGetProperty("key", out JsonElement key)) + if (propertyBag.TryGetValue("key", out JsonElement key)) { parameters.Key = key.GetString(); } @@ -314,22 +318,24 @@ internal static InvokeMethodParameters CreateInvokeMethodParameters(JsonElement { var options = new InvokeMethodParameters(); - if (parametersJson.TryGetProperty("appId", out JsonElement appId)) + var propertyBag = parametersJson.ToCaseInsensitiveDictionary(); + + if (propertyBag.TryGetValue("appid", out JsonElement appId)) { options.AppId = appId.GetRawText(); } - if (parametersJson.TryGetProperty("methodName", out JsonElement methodName)) + if (propertyBag.TryGetValue("methodname", out JsonElement methodName)) { options.MethodName = methodName.GetRawText(); } - if (parametersJson.TryGetProperty("body", out JsonElement body)) + if (propertyBag.TryGetValue("body", out JsonElement body)) { options.Body = body; } - if (parametersJson.TryGetProperty("httpVerb", out JsonElement httpVerb)) + if (propertyBag.TryGetValue("httpverb", out JsonElement httpVerb)) { options.HttpVerb = httpVerb.GetRawText(); } diff --git a/src/Microsoft.Azure.WebJobs.Extensions.Dapr/Utils/DictionaryUtils.cs b/src/Microsoft.Azure.WebJobs.Extensions.Dapr/Utils/DictionaryUtils.cs new file mode 100644 index 00000000..bac0296d --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.Dapr/Utils/DictionaryUtils.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.WebJobs.Extensions.Dapr.Utils +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + /// + /// Dictionary Utils. + /// + public static class DictionaryUtils + { + /// + /// Convert a JsonElement to a dictionary. + /// + /// JsonElement. + /// Dictionary. + public static Dictionary ToCaseInsensitiveDictionary(this JsonElement element) + { + var propertyBag = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + if (element.ValueKind == JsonValueKind.Null || element.ValueKind == JsonValueKind.Undefined) + { + return propertyBag; + } + + foreach (var prop in element.EnumerateObject()) + { + if (element.TryGetProperty(prop.Name, out JsonElement value) && value.ValueKind != JsonValueKind.Null) + { + propertyBag[prop.Name] = value; + } + } + + return propertyBag; + } + } +} \ No newline at end of file diff --git a/test/DaprExtensionTests/UnitTests/Utils/DictionaryUtilsTests.cs b/test/DaprExtensionTests/UnitTests/Utils/DictionaryUtilsTests.cs new file mode 100644 index 00000000..f1e8c4e8 --- /dev/null +++ b/test/DaprExtensionTests/UnitTests/Utils/DictionaryUtilsTests.cs @@ -0,0 +1,104 @@ +namespace DaprExtensionTests.UnitTests.Utils +{ + using System.Text.Json; + using Microsoft.Azure.WebJobs.Extensions.Dapr.Utils; + using Xunit; + + public class DictionaryUtilsTests + { + [Fact] + public void ToCaseInsensitiveDictionary_PositiveCases() + { + // Arrange + var json = @"{ + ""Name"": ""John"", + ""Age"": 30, + ""City"": ""New York"" + }"; + + var element = JsonDocument.Parse(json).RootElement; + + // Act + var dictionary = element.ToCaseInsensitiveDictionary(); + + // Assert + Assert.Equal(3, dictionary.Count); + Assert.True(dictionary.ContainsKey("name")); + Assert.True(dictionary.ContainsKey("age")); + Assert.True(dictionary.ContainsKey("city")); + Assert.Equal("John", dictionary["name"].GetString()); + Assert.Equal(30, dictionary["age"].GetInt32()); + Assert.Equal("New York", dictionary["city"].GetString()); + } + + [Fact] + public void ToCaseInsensitiveDictionary_EmptyElement() + { + // Arrange + var json = @"{}"; + + var element = JsonDocument.Parse(json).RootElement; + + // Act + var dictionary = element.ToCaseInsensitiveDictionary(); + + // Assert + Assert.Empty(dictionary); + } + + [Fact] + public void ToCaseInsensitiveDictionary_NullValueProperties() + { + // Arrange + var json = @"{ + ""Name"": null, + ""Age"": null + }"; + + var element = JsonDocument.Parse(json).RootElement; + + // Act + var dictionary = element.ToCaseInsensitiveDictionary(); + + // Assert + Assert.Empty(dictionary); + } + + [Fact] + public void ToCaseInsensitiveDictionary_NullElement() + { + // Arrange + JsonElement element = default; + + // Act + var dictionary = element.ToCaseInsensitiveDictionary(); + + // Assert + Assert.Empty(dictionary); + } + + [Fact] + public void ToCaseInsensitiveDictionary_DuplicateProperties() + { + // Arrange + var json = @"{ + ""Name"": ""John"", + ""name"": ""Doe"", + ""Age"": 30 + }"; + + var element = JsonDocument.Parse(json).RootElement; + + // Act + var dictionary = element.ToCaseInsensitiveDictionary(); + + // Assert + Assert.Equal(2, dictionary.Count); + Assert.True(dictionary.ContainsKey("name")); + Assert.True(dictionary.ContainsKey("age")); + Assert.Equal("Doe", dictionary["name"].GetString()); // The last occurrence should overwrite the previous one + Assert.Equal(30, dictionary["age"].GetInt32()); + } + } + +} diff --git a/test/DaprExtensionTests/UnitTests/Services/EnvironmentExtensionsTests.cs b/test/DaprExtensionTests/UnitTests/Utils/EnvironmentExtensionsTests.cs similarity index 99% rename from test/DaprExtensionTests/UnitTests/Services/EnvironmentExtensionsTests.cs rename to test/DaprExtensionTests/UnitTests/Utils/EnvironmentExtensionsTests.cs index f8da1a4a..53e41e84 100644 --- a/test/DaprExtensionTests/UnitTests/Services/EnvironmentExtensionsTests.cs +++ b/test/DaprExtensionTests/UnitTests/Utils/EnvironmentExtensionsTests.cs @@ -1,4 +1,4 @@ -namespace DaprExtensionTests.UnitTests.Services +namespace DaprExtensionTests.UnitTests.Utils { using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Dapr;