From 11d94d9648e082ca37719d07b7f1106d21cae57f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Habinshuti?= Date: Fri, 17 Jan 2025 18:02:29 +0300 Subject: [PATCH 1/2] Create basic failing tests --- .../ODataMessageWriterTests.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/UnitTests/Microsoft.OData.Core.Tests/ODataMessageWriterTests.cs b/test/UnitTests/Microsoft.OData.Core.Tests/ODataMessageWriterTests.cs index 489ac46852..3815fdbef2 100644 --- a/test/UnitTests/Microsoft.OData.Core.Tests/ODataMessageWriterTests.cs +++ b/test/UnitTests/Microsoft.OData.Core.Tests/ODataMessageWriterTests.cs @@ -309,6 +309,35 @@ public async Task SupportsODataUtf8JsonWriterAsync() Assert.Equal("{\"@odata.context\":\"http://www.example.com/$metadata#Edm.String\",\"value\":\"This is a test ия\"}", output); } + [Theory] + [InlineData("true", "Edm.Boolean")] + [InlineData("false", "Edm.Boolean")] + [InlineData("123", "Edm.Int32")] + [InlineData("\"This is a string\"", "Edm.String")] + public async Task SupportsTopLevelPropertyWithPrimitiveJsonElementValue(string rawValue, string expectedType) + { + // Arrange + EdmModel model = new EdmModel(); + JsonElement jsonElement = JsonDocument.Parse(rawValue).RootElement; + + // Act + string output = await WriteAndGetPayloadAsync( + model, + "application/json", + (writer) => writer.WritePropertyAsync(new ODataProperty() + { + Name = "Name", + Value = new ODataJsonElementValue(jsonElement) + }), + configureServices: (containerBuilder) => + { + containerBuilder.AddSingleton(sp => ODataUtf8JsonWriterFactory.Default); + }); + + // Assert + Assert.Equal($"{{\"@odata.context\":\"http://www.example.com/$metadata#{expectedType}\",\"value\":{rawValue}}}", output); + } + [Theory] [InlineData("utf-8")] [InlineData("utf-16")] From 57c9d4bcdcfcd49439866021240b6d7719f779cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Habinshuti?= Date: Fri, 17 Jan 2025 19:03:47 +0300 Subject: [PATCH 2/2] Extra context url type name from JsonElementValue --- .../Json/ODataJsonPropertySerializer.cs | 4 +- .../ODataContextUrlInfo.cs | 36 +++++--- .../ODataMessageWriterTests.cs | 85 ++++++++++++++++++- 3 files changed, 110 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.OData.Core/Json/ODataJsonPropertySerializer.cs b/src/Microsoft.OData.Core/Json/ODataJsonPropertySerializer.cs index 71c34dab4e..bf5399978b 100644 --- a/src/Microsoft.OData.Core/Json/ODataJsonPropertySerializer.cs +++ b/src/Microsoft.OData.Core/Json/ODataJsonPropertySerializer.cs @@ -388,7 +388,9 @@ await WriteStreamValueAsync(streamReferenceValue, property.Name, metadataBuilder return; } - if (value is ODataNullValue || value == null) + if (value is ODataNullValue + || value == null + || value is ODataJsonElementValue jsonElement && jsonElement.Value.ValueKind == System.Text.Json.JsonValueKind.Null) { await this.WriteNullPropertyAsync(property).ConfigureAwait(false); return; diff --git a/src/Microsoft.OData.Core/ODataContextUrlInfo.cs b/src/Microsoft.OData.Core/ODataContextUrlInfo.cs index da4c2ff6ce..7ee8f06119 100644 --- a/src/Microsoft.OData.Core/ODataContextUrlInfo.cs +++ b/src/Microsoft.OData.Core/ODataContextUrlInfo.cs @@ -17,6 +17,7 @@ namespace Microsoft.OData using Microsoft.OData.UriParser; using Microsoft.OData.Edm; using Microsoft.OData.Core; + using System.Text.Json; #endregion Namespaces /// @@ -454,22 +455,35 @@ private static string GetTypeNameForValue(ODataValue value, IEdmModel model) return ODataConstants.ContextUriFragmentUntyped; } - ODataPrimitiveValue primitive = value as ODataPrimitiveValue; - if (primitive == null) + if (value is ODataPrimitiveValue primitive) { - Debug.Assert(value is ODataStreamReferenceValue, "value is ODataStreamReferenceValue"); - throw new ODataException(SRResources.ODataContextUriBuilder_StreamValueMustBePropertiesOfODataResource); + + // Try convert to underlying type if the primitive value is unsigned int. + IEdmTypeDefinitionReference typeDefinitionReference = model.ResolveUIntTypeDefinition(primitive.Value); + if (typeDefinitionReference != null) + { + return typeDefinitionReference.FullName(); + } + + IEdmPrimitiveTypeReference primitiveValueTypeReference = EdmLibraryExtensions.GetPrimitiveTypeReference(primitive.Value.GetType()); + return primitiveValueTypeReference == null ? null : primitiveValueTypeReference.FullName(); } - // Try convert to underlying type if the primitive value is unsigned int. - IEdmTypeDefinitionReference typeDefinitionReference = model.ResolveUIntTypeDefinition(primitive.Value); - if (typeDefinitionReference != null) + if (value is ODataJsonElementValue jsonElementValue) { - return typeDefinitionReference.FullName(); - } + JsonElement jsonElement = jsonElementValue.Value; + return jsonElement.ValueKind switch + { + JsonValueKind.True or JsonValueKind.False => EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Boolean, true).FullName(), + JsonValueKind.Number => EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Double, true).FullName(), + JsonValueKind.String => EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.String, true).FullName(), + JsonValueKind.Null => ODataConstants.ContextUriFragmentNull, + _ => ODataConstants.ContextUriFragmentUntyped + }; + }; - IEdmPrimitiveTypeReference primitiveValueTypeReference = EdmLibraryExtensions.GetPrimitiveTypeReference(primitive.Value.GetType()); - return primitiveValueTypeReference == null ? null : primitiveValueTypeReference.FullName(); + Debug.Assert(value is ODataStreamReferenceValue, "value is ODataStreamReferenceValue"); + throw new ODataException(SRResources.ODataContextUriBuilder_StreamValueMustBePropertiesOfODataResource); } private static string CreateApplyUriSegment(ApplyClause applyClause) diff --git a/test/UnitTests/Microsoft.OData.Core.Tests/ODataMessageWriterTests.cs b/test/UnitTests/Microsoft.OData.Core.Tests/ODataMessageWriterTests.cs index 3815fdbef2..f5378d4330 100644 --- a/test/UnitTests/Microsoft.OData.Core.Tests/ODataMessageWriterTests.cs +++ b/test/UnitTests/Microsoft.OData.Core.Tests/ODataMessageWriterTests.cs @@ -19,6 +19,7 @@ using Microsoft.OData.Edm.Csdl; using Xunit; using Microsoft.OData.Core; +using System.Reflection.Metadata; namespace Microsoft.OData.Tests { @@ -312,14 +313,14 @@ public async Task SupportsODataUtf8JsonWriterAsync() [Theory] [InlineData("true", "Edm.Boolean")] [InlineData("false", "Edm.Boolean")] - [InlineData("123", "Edm.Int32")] + [InlineData("123", "Edm.Double")] [InlineData("\"This is a string\"", "Edm.String")] - public async Task SupportsTopLevelPropertyWithPrimitiveJsonElementValue(string rawValue, string expectedType) + public async Task SupportsTopLevelPropertyWithPrimitiveJsonElementValueAsync(string rawValue, string expectedType) { // Arrange EdmModel model = new EdmModel(); JsonElement jsonElement = JsonDocument.Parse(rawValue).RootElement; - + // Act string output = await WriteAndGetPayloadAsync( model, @@ -338,6 +339,84 @@ public async Task SupportsTopLevelPropertyWithPrimitiveJsonElementValue(string r Assert.Equal($"{{\"@odata.context\":\"http://www.example.com/$metadata#{expectedType}\",\"value\":{rawValue}}}", output); } + [Theory] + [InlineData("[1,2,3]")] + [InlineData("{\"foo\":\"bar\"}")] + public async Task WritesUntypedContextJsonElementTopLevelPropertyWithArrayOrObject(string rawValue) + { + // Arrange + EdmModel model = new EdmModel(); + JsonElement jsonElement = JsonDocument.Parse(rawValue).RootElement; + + // Act + string output = await WriteAndGetPayloadAsync( + model, + "application/json", + (writer) => writer.WritePropertyAsync(new ODataProperty() + { + Name = "Name", + Value = new ODataJsonElementValue(jsonElement) + }), + configureServices: (containerBuilder) => + { + containerBuilder.AddSingleton(sp => ODataUtf8JsonWriterFactory.Default); + }); + + // Assert + Assert.Equal($"{{\"@odata.context\":\"http://www.example.com/$metadata#Edm.Untyped\",\"value\":{rawValue}}}", output); + } + + [Theory] + [InlineData(true, "Edm.Boolean")] + [InlineData(false, "Edm.Boolean")] + [InlineData(123, "Edm.Int32")] + [InlineData(123.0, "Edm.Double")] + [InlineData("\"This is a string\"", "Edm.String")] + public async Task SupportsTopLevelPropertyWithPrimitiveValueAsync(object value, string expectedType) + { + // Arrange + EdmModel model = new EdmModel(); + + // Act + string output = await WriteAndGetPayloadAsync( + model, + "application/json", + (writer) => writer.WritePropertyAsync(new ODataProperty() + { + Name = "Name", + Value = value + }), + configureServices: (containerBuilder) => + { + containerBuilder.AddSingleton(sp => ODataUtf8JsonWriterFactory.Default); + }); + + // Assert + Assert.Equal($"{{\"@odata.context\":\"http://www.example.com/$metadata#{expectedType}\",\"value\":{value}}}", output); + } + + [Fact] + public async Task ThrowsExceptionIfJsonElementValueContainsNullInTopLevelPropertyAsync() + { + // Arrange + EdmModel model = new EdmModel(); + JsonElement jsonElement = JsonDocument.Parse("null").RootElement; + + // Act + var testCode = async () => await WriteAndGetPayloadAsync( + model, + "application/json", + (writer) => writer.WritePropertyAsync(new ODataProperty() + { + Name = "Name", + Value = new ODataJsonElementValue(jsonElement) + })); + + // Assert + var exception = await Assert.ThrowsAsync(testCode); + Assert.Equal("Cannot write the value 'null' in top level property; return 204 instead.", exception.Message); + } + [Theory] [InlineData("utf-8")] [InlineData("utf-16")]