Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 2 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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