Skip to content

Commit

Permalink
fixes #2659: Support declared Edm.Untyped property serialization usin…
Browse files Browse the repository at this point in the history
…g ODataPrimitiveValue, etc (#2664)

* fixes #2659: Support declared Edm.Untyped property serialization using ODataPrimitiveValue, etc

* Fixes the failing tests
  • Loading branch information
xuzhg authored May 12, 2023
1 parent 399df92 commit 5638479
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 10 deletions.
5 changes: 4 additions & 1 deletion src/Microsoft.OData.Core/TypeNameOracle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
7 changes: 7 additions & 0 deletions src/Microsoft.OData.Core/ValidationUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
23 changes: 15 additions & 8 deletions src/Microsoft.OData.Core/WriterValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -48,15 +50,201 @@ 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
{
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<ODataException>(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()
{
Expand Down

0 comments on commit 5638479

Please sign in to comment.