From 4e9187cf3b06018d9d80960d4cb1a1576ef6bd31 Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Wed, 10 May 2023 18:15:47 -0700 Subject: [PATCH 1/2] fixes #2659: Support declared Edm.Untyped property serialization using ODataPrimitiveValue, etc --- src/Microsoft.OData.Core/TypeNameOracle.cs | 5 +- src/Microsoft.OData.Core/ValidationUtils.cs | 7 + src/Microsoft.OData.Core/WriterValidator.cs | 23 ++- ...htEntryAndFeedSerializerUndecalredTests.cs | 190 +++++++++++++++++- 4 files changed, 215 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.OData.Core/TypeNameOracle.cs b/src/Microsoft.OData.Core/TypeNameOracle.cs index 4cc1b4b465..142ae5968c 100644 --- a/src/Microsoft.OData.Core/TypeNameOracle.cs +++ b/src/Microsoft.OData.Core/TypeNameOracle.cs @@ -318,7 +318,10 @@ private static IEdmTypeReference ResolveTypeFromMetadataAndValue(IEdmTypeReferen return typeReferenceFromMetadata; } - Debug.Assert(typeReferenceFromValue.TypeKind() == typeReferenceFromMetadata.TypeKind(), "typeReferenceFromValue.TypeKind() == typeReferenceFromMetadata.TypeKind()"); + if (!typeReferenceFromMetadata.IsUntyped()) + { + Debug.Assert(typeReferenceFromValue.TypeKind() == typeReferenceFromMetadata.TypeKind(), "typeReferenceFromValue.TypeKind() == typeReferenceFromMetadata.TypeKind()"); + } writerValidator.ValidateTypeReference(typeReferenceFromMetadata, typeReferenceFromValue); diff --git a/src/Microsoft.OData.Core/ValidationUtils.cs b/src/Microsoft.OData.Core/ValidationUtils.cs index 0f0d661533..09c45b2e44 100644 --- a/src/Microsoft.OData.Core/ValidationUtils.cs +++ b/src/Microsoft.OData.Core/ValidationUtils.cs @@ -302,6 +302,13 @@ internal static void ValidateIsExpectedPrimitiveType(object value, IEdmPrimitive } Debug.Assert(valuePrimitiveTypeReference.IsEquivalentTo(EdmLibraryExtensions.GetPrimitiveTypeReference(value.GetType())), "The value and valuePrimitiveTypeReference don't match."); + + // If the expected type is 'Edm.Untyped', we don't need to verify the value type. + if (expectedTypeReference.IsUntyped()) + { + return; + } + if (!expectedTypeReference.IsODataPrimitiveTypeKind() && !expectedTypeReference.IsODataTypeDefinitionTypeKind()) { // non-primitive type found for primitive value. diff --git a/src/Microsoft.OData.Core/WriterValidator.cs b/src/Microsoft.OData.Core/WriterValidator.cs index 6246e9b7dc..32cd12579d 100644 --- a/src/Microsoft.OData.Core/WriterValidator.cs +++ b/src/Microsoft.OData.Core/WriterValidator.cs @@ -151,8 +151,12 @@ public virtual void ValidateTypeReference(IEdmTypeReference typeReferenceFromMet { if (settings.ThrowIfTypeConflictsWithMetadata) { + if (typeReferenceFromMetadata.IsUntyped()) + { + ; // do nothing here + } // Make sure the types are the same - if (typeReferenceFromValue.IsODataPrimitiveTypeKind()) + else if (typeReferenceFromValue.IsODataPrimitiveTypeKind()) { // Primitive types must match exactly except for nullability ValidationUtils.ValidateMetadataPrimitiveType(typeReferenceFromMetadata, @@ -172,14 +176,17 @@ public virtual void ValidateTypeReference(IEdmTypeReference typeReferenceFromMet } else if (typeReferenceFromMetadata.IsCollection()) { - // Collection types must match exactly. - if (!typeReferenceFromMetadata.Definition.IsElementTypeEquivalentTo( - typeReferenceFromValue.Definition)) + if (!typeReferenceFromMetadata.AsCollection().ElementType().IsUntyped()) { - throw new ODataException( - Strings.ValidationUtils_IncompatibleType( - typeReferenceFromValue.FullName(), - typeReferenceFromMetadata.FullName())); + // Collection types must match exactly. + if (!typeReferenceFromMetadata.Definition.IsElementTypeEquivalentTo( + typeReferenceFromValue.Definition)) + { + throw new ODataException( + Strings.ValidationUtils_IncompatibleType( + typeReferenceFromValue.FullName(), + typeReferenceFromMetadata.FullName())); + } } } else diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs index 641d82b10f..9b1f2c40c2 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs @@ -35,6 +35,8 @@ public ODataJsonLightEntryAndFeedSerializerUndeclaredTests() this.serverEntityType = new EdmEntityType("Server.NS", "ServerEntityType"); this.serverModel.AddElement(this.serverEntityType); this.serverEntityType.AddKeys(this.serverEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + this.serverEntityType.AddStructuralProperty("Data", EdmCoreModel.Instance.GetUntyped()); + this.serverEntityType.AddStructuralProperty("Infos", new EdmCollectionTypeReference(new EdmCollectionType(EdmCoreModel.Instance.GetUntyped()))); this.serverEntityType.AddStructuralProperty("Address", new EdmComplexTypeReference(addressType, true)); // open entity type @@ -48,6 +50,11 @@ public ODataJsonLightEntryAndFeedSerializerUndeclaredTests() this.serverEntitySet = container.AddEntitySet("serverEntitySet", this.serverEntityType); this.serverOpenEntitySet = container.AddEntitySet("serverOpenEntitySet", this.serverOpenEntityType); this.serverModel.AddElement(container); + + EdmEnumType enumType = new EdmEnumType("Server.NS", "EnumType"); + enumType.AddMember(new EdmEnumMember(enumType, "Red", new EdmEnumMemberValue(1))); + enumType.AddMember(new EdmEnumMember(enumType, "Green", new EdmEnumMemberValue(2))); + this.serverModel.AddElement(enumType); } private ODataMessageWriterSettings writerSettings = new ODataMessageWriterSettings @@ -55,8 +62,189 @@ public ODataJsonLightEntryAndFeedSerializerUndeclaredTests() Validations = ~ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType }; - #region non-open entity's property unknown name + known value type + #region Declared Untyped Properties + [Fact] + public void WriteResourceDeclaredSingleUntypedProperty_WorksForUntypedValue() + { + var property = new ODataProperty { Name = "Data", Value = new ODataUntypedValue { RawValue = "'#lje324$$'" } }; + string result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data\":\"#lje324$$\"}", result); + } + + [Fact] + public void WriteResourceDeclaredSingleUntypedProperty_WorksForPrimitiveValue() + { + // String is one of default type + var property = new ODataProperty { Name = "Data", Value = new ODataPrimitiveValue("41") }; + string result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data@odata.type\":\"#String\",\"Data\":\"41\"}", result); + + // not-default type + property = new ODataProperty { Name = "Data", Value = new ODataPrimitiveValue(41) }; + result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data@odata.type\":\"#Int32\",\"Data\":41}", result); + } + + [Fact] + public void WriteResourceDeclaredSingleUntypedProperty_WorksForEnumValue() + { + // Without type name + var property = new ODataProperty { Name = "Data", Value = new ODataEnumValue("AnyMem") }; + string result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data\":\"AnyMem\"}", result); + + // with type name + property = new ODataProperty { Name = "Data", Value = new ODataEnumValue("Green", "Server.NS.EnumType") }; + result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data\":\"Green\"}", result); + } + + [Fact] + public void WriteResourceDeclaredSingleUntypedProperty_WorksForBinaryStreamValue() + { + // With type name + // ODataBinaryStreamValue + var stream = new MemoryStream(); + var writer = new BinaryWriter(stream); + writer.Write("1234567890"); + writer.Flush(); + stream.Position = 0; + + var property = new ODataProperty + { + Name = "Data", + Value = new ODataBinaryStreamValue(stream) + }; + + string result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data\":\"CjEyMzQ1Njc4OTA=\"}", result); + } + + [Fact] + public void WriteResourceDeclaredSingleUntypedProperty_WorksForCollectionValue() + { + // With type name + var property = new ODataProperty + { + Name = "Data", + Value = new ODataCollectionValue + { + TypeName = "Collection(Edm.String)", + Items = new[] + { + "abc", + "xyz" + } + } + }; + + string result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data\":[\"abc\",\"xyz\"]}", result); + + // without type name + property = new ODataProperty + { + Name = "Data", + Value = new ODataCollectionValue + { + Items = new object[] + { + "abc", + null, + 42 + } + } + }; + + result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data\":[\"abc\",null,42]}", result); + } + [Fact] + public void WriteResourceDeclaredCollectionUntypedProperty_ThrowsForNonCollectionValue() + { + // With type name + var property = new ODataProperty + { + Name = "Infos", + Value = new ODataPrimitiveValue(42) + }; + + Action test = () => WriteDeclaredUntypedProperty(property); + ODataException exception = Assert.Throws(test); + Assert.Equal(Strings.ValidationUtils_NonPrimitiveTypeForPrimitiveValue("Collection(Edm.Untyped)"), exception.Message); + } + + [Fact] + public void WriteResourceDeclaredCollectionUntypedProperty_WorksForCollectionValue() + { + var property = new ODataProperty + { + Name = "Infos", + Value = new ODataCollectionValue + { + Items = new object[] + { + "abc", + null, + 42 + } + } + }; + + string result = WriteDeclaredUntypedProperty(property); + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Infos\":[\"abc\",null,42]}", result); + } + + private string WriteDeclaredUntypedProperty(ODataProperty untypedProperty) + { + var entry = new ODataResource + { + TypeName = "Server.NS.ServerEntityType", + Properties = new[] + { + untypedProperty + } + }; + + return this.WriteEntryPayload(this.serverEntitySet, this.serverEntityType, + writer => + { + writer.WriteStart(entry); + writer.WriteEnd(); + }); + } + + [Fact] + public void WriteResourceDeclaredUntypedProperty_WorksForNestedResourceInfo() + { + string result = WriteEntryPayload(this.serverEntitySet, this.serverEntityType, + writer => + { + writer.WriteStart(new ODataResource()); + + writer.WriteStart(new ODataNestedResourceInfo { Name = "Data", IsCollection = true }); + writer.WriteStart(new ODataResourceSet()); + writer.WriteStart(new ODataResource { TypeName = "Edm.Untyped" }); + writer.WriteEnd(); + writer.WriteEnd(); + writer.WriteEnd(); + + writer.WriteStart(new ODataNestedResourceInfo { Name = "Infos", IsCollection = true }); + writer.WriteStart(new ODataResourceSet()); + writer.WriteStart(resource: null); + writer.WriteEnd(); + writer.Write(new ODataPrimitiveValue(32)); + writer.WriteEnd(); + writer.WriteEnd(); + writer.WriteEnd(); + }); + + Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data\":[{}],\"Infos\":[null,32]}", result); + } + #endregion + + #region non-open entity's property unknown name + known value type [Fact] public void WriteEntryUndeclaredPropertiesTest() { From 5447f2c57390f2a9d87e28820682a6dea6634823 Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Thu, 11 May 2023 10:05:25 -0700 Subject: [PATCH 2/2] Fixes the failing tests --- .../ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs index 9b1f2c40c2..43a27d0b57 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightEntryAndFeedSerializerUndecalredTests.cs @@ -66,7 +66,7 @@ public ODataJsonLightEntryAndFeedSerializerUndeclaredTests() [Fact] public void WriteResourceDeclaredSingleUntypedProperty_WorksForUntypedValue() { - var property = new ODataProperty { Name = "Data", Value = new ODataUntypedValue { RawValue = "'#lje324$$'" } }; + var property = new ODataProperty { Name = "Data", Value = new ODataUntypedValue { RawValue = "\"#lje324$$\"" } }; string result = WriteDeclaredUntypedProperty(property); Assert.Equal("{\"@odata.context\":\"http://www.sampletest.com/$metadata#serverEntitySet/$entity\",\"Data\":\"#lje324$$\"}", result); }