diff --git a/src/Microsoft.OData.Core/Build.Net35/Microsoft.OData.Core.NetFX35.csproj b/src/Microsoft.OData.Core/Build.Net35/Microsoft.OData.Core.NetFX35.csproj index 78d23ba359..2efa83fe90 100644 --- a/src/Microsoft.OData.Core/Build.Net35/Microsoft.OData.Core.NetFX35.csproj +++ b/src/Microsoft.OData.Core/Build.Net35/Microsoft.OData.Core.NetFX35.csproj @@ -1,4 +1,4 @@ - + true @@ -308,26 +308,26 @@ Microsoft\OData\Core\JsonLight\ODataJsonLightWriterUtils.cs - Microsoft\OData\Core\JsonLight\ReorderingJsonReader.cs - + Microsoft\OData\Core\JsonLight\ReorderingJsonReader.cs + Microsoft\OData\Core\JsonLight\ODataJsonLightBatchBodyContentReaderStream.cs Microsoft\OData\Core\JsonLight\ODataJsonLightBatchReader.cs - + Microsoft\OData\Core\JsonLight\ODataJsonLightBatchWriter.cs - + Microsoft\OData\Core\JsonLight\ODataJsonLightBatchPayloadItemPropertiesCache.cs - + Microsoft\OData\Core\JsonLight\ODataJsonLightBatchReaderStream.cs - + Microsoft\OData\Core\JsonLight\ODataJsonLightBatchAtomicGroupCache.cs - + Microsoft\OData\Core\Json\BufferingJsonReader.cs @@ -453,13 +453,13 @@ Microsoft\OData\Core\MultipartMixed\ODataMultipartMixedBatchInputContext.cs - + Microsoft\OData\Core\MultipartMixed\ODataMultipartMixedBatchOutputContext.cs Microsoft\OData\Core\MultipartMixed\ODataMultipartMixedBatchReader.cs - + Microsoft\OData\Core\MultipartMixed\ODataMultipartMixedBatchReaderStream.cs @@ -471,7 +471,7 @@ Microsoft\OData\Core\MultipartMixed\DependsOnIdsTracker.cs - + Microsoft\OData\Core\NonDisposingStream.cs @@ -1356,7 +1356,7 @@ Microsoft\OData\Core\UriParser\SyntacticAst\QueryTokenKind.cs - + Microsoft\OData\Core\UriParser\SyntacticAst\RangeVariableToken.cs @@ -1554,6 +1554,9 @@ Microsoft\OData\Core\ODataEdmPropertyAnnotation.cs + + Microsoft\OData\Core\ODataLibraryCompatibility.cs + Microsoft\OData\Core\ODataNestedResourceInfoSerializationInfo.cs diff --git a/src/Microsoft.OData.Core/Build.NetStandard/Microsoft.OData.Core.NetStandard.csproj b/src/Microsoft.OData.Core/Build.NetStandard/Microsoft.OData.Core.NetStandard.csproj index b37efb7c07..b719ae0f8a 100644 --- a/src/Microsoft.OData.Core/Build.NetStandard/Microsoft.OData.Core.NetStandard.csproj +++ b/src/Microsoft.OData.Core/Build.NetStandard/Microsoft.OData.Core.NetStandard.csproj @@ -1,4 +1,4 @@ - + Microsoft.OData.Core @@ -292,7 +292,7 @@ JsonLight\ODataJsonLightBatchAtomicGroupCache.cs - + JsonLight\ODataJsonLightBatchPayloadItemPropertiesCache.cs @@ -301,13 +301,13 @@ JsonLight\ODataJsonLightBatchWriter.cs - + JsonLight\ODataJsonLightBatchReaderStream.cs JsonLight\ODataJsonLightBatchBodyContentReaderStream.cs - + JsonLight\ODataJsonLightParameterDeserializer.cs @@ -499,7 +499,7 @@ MultipartMixed\ODataMultipartMixedBatchReader.cs - + MultipartMixed\ODataMultipartMixedBatchReaderStream.cs @@ -508,7 +508,7 @@ MultipartMixed\ODataMultipartMixedBatchWriterUtils.cs - + MultipartMixed\DependsOnIdsTracker.cs @@ -707,6 +707,9 @@ ODataJsonDateTimeFormat.cs + + ODataLibraryCompatibility.cs + ODataMediaType.cs @@ -982,7 +985,7 @@ TypeUtils.cs - + UnknownEntitySet.cs diff --git a/src/Microsoft.OData.Core/IWriterValidator.cs b/src/Microsoft.OData.Core/IWriterValidator.cs index 7174f25b66..ce1171dd84 100644 --- a/src/Microsoft.OData.Core/IWriterValidator.cs +++ b/src/Microsoft.OData.Core/IWriterValidator.cs @@ -96,9 +96,10 @@ void ValidateTypeKind(EdmTypeKind actualTypeKind, /// The expected property type or null if we /// don't have any. /// The name of the property. + /// true if the property is top-level. /// The model used to get the OData version. void ValidateNullPropertyValue(IEdmTypeReference expectedPropertyTypeReference, - string propertyName, IEdmModel model); + string propertyName, bool isTopLevel, IEdmModel model); /// /// Validates a null collection item against the expected type. diff --git a/src/Microsoft.OData.Core/JsonLight/JsonLightConstants.cs b/src/Microsoft.OData.Core/JsonLight/JsonLightConstants.cs index 0bcb9d0b5f..0a34148841 100644 --- a/src/Microsoft.OData.Core/JsonLight/JsonLightConstants.cs +++ b/src/Microsoft.OData.Core/JsonLight/JsonLightConstants.cs @@ -20,6 +20,13 @@ internal static class JsonLightConstants /// The separator of property annotations. internal const char ODataPropertyAnnotationSeparatorChar = '@'; + /// + /// The 'null' property name for the Json Light value property. + /// This is an OData 3.0 protocol element used for compatibility + /// with 6.x library version. + /// + internal const string ODataNullPropertyName = "null"; + /// The value 'true' for the OData null annotation. internal const string ODataNullAnnotationTrueValue = "true"; diff --git a/src/Microsoft.OData.Core/JsonLight/ODataAnnotationNames.cs b/src/Microsoft.OData.Core/JsonLight/ODataAnnotationNames.cs index c977c312c8..3b9ea27a9a 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataAnnotationNames.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataAnnotationNames.cs @@ -42,7 +42,8 @@ internal static class ODataAnnotationNames ODataNavigationLinkUrl, ODataDeltaLink, ODataRemoved, - ODataDelta + ODataDelta, + ODataNull, }, StringComparer.Ordinal); @@ -100,6 +101,12 @@ internal static class ODataAnnotationNames /// The 'odata.delta' annotation name. internal const string ODataDelta = "odata.delta"; + /// + /// The OData Null annotation name. This is an OData 3.0 protocol element + /// used for compatibility with 6.x library version. + /// + internal const string ODataNull = "odata.null"; + /// /// Returns true if the starts with "odata.", false otherwise. /// diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertyAndValueDeserializer.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertyAndValueDeserializer.cs index 4d918d6dc4..1383a86a5e 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertyAndValueDeserializer.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertyAndValueDeserializer.cs @@ -1093,116 +1093,136 @@ private ODataProperty ReadTopLevelPropertyImplementation(IEdmTypeReference expec object propertyValue = missingPropertyValue; var customInstanceAnnotations = new Collection(); - string payloadTypeName = null; - if (this.ReadingComplexProperty(propertyAndAnnotationCollector, expectedPropertyTypeReference, out payloadTypeName)) + // Check for the special top-level OData 3.0 null marker in order to accommodate + // the responses written by 6.x version of this library. + if (this.IsTopLevel6xNullValue()) { - // Figure out whether we are reading a complex property or not; complex properties are not wrapped while all others are. - // Since we don't have metadata in all cases (open properties), we have to detect the type in some cases. - this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); - - // Now read the property value - propertyValue = this.ReadNonEntityValue( - payloadTypeName, + // NOTE: when reading a null value we will never ask the type resolver (if present) to resolve the + // type; we always fall back to the expected type. + this.ReaderValidator.ValidateNullValue( expectedPropertyTypeReference, - propertyAndAnnotationCollector, - /*collectionValidator*/ null, /*validateNullValue*/ true, - /*isTopLevelPropertyValue*/ true, - /*insideComplexValue*/ true, - /*propertyName*/ null); + /*propertyName*/ null, + null); + + // We don't allow properties or non-custom annotations in the null payload. + this.ValidateNoPropertyInNullPayload(propertyAndAnnotationCollector); + + propertyValue = null; } else { - bool isReordering = this.JsonReader is ReorderingJsonReader; - - Func propertyAnnotationReaderForTopLevelProperty = - annotationName => { throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedODataPropertyAnnotation(annotationName)); }; - - // Read through all top-level properties, ignore the ones with reserved names (i.e., reserved - // characters in their name) and throw if we find none or more than one properties without reserved name. - while (this.JsonReader.NodeType == JsonNodeType.Property) + string payloadTypeName = null; + if (this.ReadingComplexProperty(propertyAndAnnotationCollector, expectedPropertyTypeReference, out payloadTypeName)) { - this.ProcessProperty( + // Figure out whether we are reading a complex property or not; complex properties are not wrapped while all others are. + // Since we don't have metadata in all cases (open properties), we have to detect the type in some cases. + this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); + + // Now read the property value + propertyValue = this.ReadNonEntityValue( + payloadTypeName, + expectedPropertyTypeReference, propertyAndAnnotationCollector, - propertyAnnotationReaderForTopLevelProperty, - (propertyParsingResult, propertyName) => - { - switch (propertyParsingResult) + /*collectionValidator*/ null, + /*validateNullValue*/ true, + /*isTopLevelPropertyValue*/ true, + /*insideComplexValue*/ true, + /*propertyName*/ null); + } + else + { + bool isReordering = this.JsonReader is ReorderingJsonReader; + + Func propertyAnnotationReaderForTopLevelProperty = + annotationName => { throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedODataPropertyAnnotation(annotationName)); }; + + // Read through all top-level properties, ignore the ones with reserved names (i.e., reserved + // characters in their name) and throw if we find none or more than one properties without reserved name. + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + this.ProcessProperty( + propertyAndAnnotationCollector, + propertyAnnotationReaderForTopLevelProperty, + (propertyParsingResult, propertyName) => { - case PropertyParsingResult.ODataInstanceAnnotation: - if (string.CompareOrdinal(ODataAnnotationNames.ODataType, propertyName) == 0) - { - // When we are not using the reordering reader we have to ensure that the 'odata.type' property appears before - // the 'value' property; otherwise we already scanned ahead and read the type name and have to now - // ignore it (even if it is after the 'value' property). - if (isReordering) + switch (propertyParsingResult) + { + case PropertyParsingResult.ODataInstanceAnnotation: + if (string.CompareOrdinal(ODataAnnotationNames.ODataType, propertyName) == 0) { - this.JsonReader.SkipValue(); + // When we are not using the reordering reader we have to ensure that the 'odata.type' property appears before + // the 'value' property; otherwise we already scanned ahead and read the type name and have to now + // ignore it (even if it is after the 'value' property). + if (isReordering) + { + this.JsonReader.SkipValue(); + } + else + { + if (!object.ReferenceEquals(missingPropertyValue, propertyValue)) + { + throw new ODataException( + ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_TypePropertyAfterValueProperty(ODataAnnotationNames.ODataType, JsonLightConstants.ODataValuePropertyName)); + } + + payloadTypeName = this.ReadODataTypeAnnotationValue(); + } } else { - if (!object.ReferenceEquals(missingPropertyValue, propertyValue)) - { - throw new ODataException( - ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_TypePropertyAfterValueProperty(ODataAnnotationNames.ODataType, JsonLightConstants.ODataValuePropertyName)); - } + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties(propertyName)); + } - payloadTypeName = this.ReadODataTypeAnnotationValue(); + break; + case PropertyParsingResult.CustomInstanceAnnotation: + ODataAnnotationNames.ValidateIsCustomAnnotationName(propertyName); + Debug.Assert( + !this.MessageReaderSettings.ShouldSkipAnnotation(propertyName), + "!this.MessageReaderSettings.ShouldReadAndValidateAnnotation(annotationName) -- otherwise we should have already skipped the custom annotation and won't see it here."); + var customInstanceAnnotationValue = this.ReadCustomInstanceAnnotationValue(propertyAndAnnotationCollector, propertyName); + customInstanceAnnotations.Add(new ODataInstanceAnnotation(propertyName, customInstanceAnnotationValue.ToODataValue())); + break; + + case PropertyParsingResult.PropertyWithoutValue: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_TopLevelPropertyAnnotationWithoutProperty(propertyName)); + + case PropertyParsingResult.PropertyWithValue: + if (string.CompareOrdinal(JsonLightConstants.ODataValuePropertyName, propertyName) == 0) + { + // Now read the property value + propertyValue = this.ReadNonEntityValue( + payloadTypeName, + expectedPropertyTypeReference, + /*propertyAndAnnotationCollector*/ null, + /*collectionValidator*/ null, + /*validateNullValue*/ true, + /*isTopLevelPropertyValue*/ true, + /*insideComplexValue*/ false, + /*propertyName*/ propertyName); + } + else + { + throw new ODataException( + ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyName(propertyName, JsonLightConstants.ODataValuePropertyName)); } - } - else - { - throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties(propertyName)); - } - - break; - case PropertyParsingResult.CustomInstanceAnnotation: - ODataAnnotationNames.ValidateIsCustomAnnotationName(propertyName); - Debug.Assert( - !this.MessageReaderSettings.ShouldSkipAnnotation(propertyName), - "!this.MessageReaderSettings.ShouldReadAndValidateAnnotation(annotationName) -- otherwise we should have already skipped the custom annotation and won't see it here."); - var customInstanceAnnotationValue = this.ReadCustomInstanceAnnotationValue(propertyAndAnnotationCollector, propertyName); - customInstanceAnnotations.Add(new ODataInstanceAnnotation(propertyName, customInstanceAnnotationValue.ToODataValue())); - break; - - case PropertyParsingResult.PropertyWithoutValue: - throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_TopLevelPropertyAnnotationWithoutProperty(propertyName)); - - case PropertyParsingResult.PropertyWithValue: - if (string.CompareOrdinal(JsonLightConstants.ODataValuePropertyName, propertyName) == 0) - { - // Now read the property value - propertyValue = this.ReadNonEntityValue( - payloadTypeName, - expectedPropertyTypeReference, - /*propertyAndAnnotationCollector*/ null, - /*collectionValidator*/ null, - /*validateNullValue*/ true, - /*isTopLevelPropertyValue*/ true, - /*insideComplexValue*/ false, - /*propertyName*/ propertyName); - } - else - { - throw new ODataException( - ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyName(propertyName, JsonLightConstants.ODataValuePropertyName)); - } - - break; - - case PropertyParsingResult.EndOfObject: - break; - - case PropertyParsingResult.MetadataReferenceProperty: - throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty(propertyName)); - } - }); - } - if (object.ReferenceEquals(missingPropertyValue, propertyValue)) - { - // No property found; there should be exactly one property in the top-level property wrapper that does not have a reserved name. - throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyPayload); + break; + + case PropertyParsingResult.EndOfObject: + break; + + case PropertyParsingResult.MetadataReferenceProperty: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty(propertyName)); + } + }); + } + + if (object.ReferenceEquals(missingPropertyValue, propertyValue)) + { + // No property found; there should be exactly one property in the top-level property wrapper that does not have a reserved name. + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_InvalidTopLevelPropertyPayload); + } } } @@ -1742,6 +1762,72 @@ private bool ReadingComplexProperty(PropertyAndAnnotationCollector propertyAndAn return readingComplexProperty; } + /// + /// Tries to read a top-level null value from the JSON reader. + /// + /// true if a null value could be read from the JSON reader; otherwise false. + /// If the method detects the odata.null annotation, it will read it; otherwise the reader does not move. + private bool IsTopLevel6xNullValue() + { + bool odataNullAnnotationInPayload = this.JsonReader.NodeType == JsonNodeType.Property && string.CompareOrdinal(JsonLightConstants.ODataPropertyAnnotationSeparatorChar + ODataAnnotationNames.ODataNull, JsonReader.GetPropertyName()) == 0; + if (odataNullAnnotationInPayload) + { + // If we found the expected annotation read over the property name + this.JsonReader.ReadNext(); + + // Now check the value of the annotation + object nullAnnotationValue = this.JsonReader.ReadPrimitiveValue(); + if (!(nullAnnotationValue is bool) || (bool)nullAnnotationValue == false) + { + throw new ODataException(ODataErrorStrings.ODataJsonLightReaderUtils_InvalidValueForODataNullAnnotation(ODataAnnotationNames.ODataNull, JsonLightConstants.ODataNullAnnotationTrueValue)); + } + } + + return odataNullAnnotationInPayload; + } + + /// + /// Make sure that we don't find any other odata.* annotations or properties after reading a payload with the odata.null annotation. + /// + /// The duplicate property names checker to use. + private void ValidateNoPropertyInNullPayload(PropertyAndAnnotationCollector propertyAndAnnotationCollector) + { + Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != null"); + + // we use the ParseProperty method to ignore custom annotations. + Func propertyAnnotationReaderForTopLevelNull = annotationName => { throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedODataPropertyAnnotation(annotationName)); }; + while (this.JsonReader.NodeType == JsonNodeType.Property) + { + this.ProcessProperty( + propertyAndAnnotationCollector, + propertyAnnotationReaderForTopLevelNull, + (propertyParsingResult, propertyName) => + { + switch (propertyParsingResult) + { + case PropertyParsingResult.ODataInstanceAnnotation: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedAnnotationProperties(propertyName)); + + case PropertyParsingResult.CustomInstanceAnnotation: + this.JsonReader.SkipValue(); + break; + + case PropertyParsingResult.PropertyWithoutValue: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_TopLevelPropertyAnnotationWithoutProperty(propertyName)); + + case PropertyParsingResult.PropertyWithValue: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_NoPropertyAndAnnotationAllowedInNullPayload(propertyName)); + + case PropertyParsingResult.EndOfObject: + break; + + case PropertyParsingResult.MetadataReferenceProperty: + throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty(propertyName)); + } + }); + } + } + /// /// Increases the recursion depth of values by 1. This will throw if the recursion depth exceeds the current limit. /// diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs index 18b7978319..eb8d87cdec 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs @@ -65,12 +65,6 @@ internal void WriteTopLevelProperty(ODataProperty property) Debug.Assert(property != null, "property != null"); Debug.Assert(!(property.Value is ODataStreamReferenceValue), "!(property.Value is ODataStreamReferenceValue)"); - if (property.ODataValue == null || property.ODataValue.IsNullValue) - { - // TODO: Enable updating top-level properties to null #645 - throw new ODataException("A null top-level property is not allowed to be serialized."); - } - this.WriteTopLevelPayload( () => { @@ -366,12 +360,27 @@ private void WriteNullProperty( ODataProperty property) { this.WriterValidator.ValidateNullPropertyValue( - this.currentPropertyInfo.MetadataType.TypeReference, property.Name, this.Model); + this.currentPropertyInfo.MetadataType.TypeReference, property.Name, + this.currentPropertyInfo.IsTopLevel, this.Model); if (this.currentPropertyInfo.IsTopLevel) { - // TODO: Enable updating top-level properties to null #645 - throw new ODataException("A null top-level property is not allowed to be serialized."); + if (this.JsonLightOutputContext.MessageWriterSettings.LibraryCompatibility < + ODataLibraryCompatibility.Version7) + { + // The 6.x library used an OData 3.0 protocol element in this case: @odata.null=true + this.ODataAnnotationWriter.WriteInstanceAnnotationName(ODataAnnotationNames.ODataNull); + this.JsonWriter.WriteValue(true); + } + else + { + // From the spec: + // 11.2.3 Requesting Individual Properties + // ... + // If the property is single-valued and has the null value, the service responds with 204 No Content. + // ... + throw new ODataException(Strings.ODataMessageWriter_CannotWriteTopLevelNull); + } } else { diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs index 588c149fe9..ea94bc7d37 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs @@ -128,6 +128,7 @@ internal sealed class TextRes { internal const string ODataMessageWriter_MetadataDocumentInRequest = "ODataMessageWriter_MetadataDocumentInRequest"; internal const string ODataMessageWriter_DeltaInRequest = "ODataMessageWriter_DeltaInRequest"; internal const string ODataMessageWriter_AsyncInRequest = "ODataMessageWriter_AsyncInRequest"; + internal const string ODataMessageWriter_CannotWriteTopLevelNull = "ODataMessageWriter_CannotWriteTopLevelNull"; internal const string ODataMessageWriter_CannotWriteNullInRawFormat = "ODataMessageWriter_CannotWriteNullInRawFormat"; internal const string ODataMessageWriter_CannotSetHeadersWithInvalidPayloadKind = "ODataMessageWriter_CannotSetHeadersWithInvalidPayloadKind"; internal const string ODataMessageWriter_IncompatiblePayloadKinds = "ODataMessageWriter_IncompatiblePayloadKinds"; diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.csproj b/src/Microsoft.OData.Core/Microsoft.OData.Core.csproj index a2776f9528..c0dec76eca 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.csproj +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.csproj @@ -53,6 +53,7 @@ + diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.txt b/src/Microsoft.OData.Core/Microsoft.OData.Core.txt index 0aad37d499..43cb1a4ddf 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.txt +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.txt @@ -79,6 +79,7 @@ ODataMessageWriter_ServiceDocumentInRequest=A service document cannot be written ODataMessageWriter_MetadataDocumentInRequest=A metadata document cannot be written to request payloads. Metadata documents are only supported in responses. ODataMessageWriter_DeltaInRequest=Cannot write delta in request payload. ODataMessageWriter_AsyncInRequest=Cannot write async in request payload. +ODataMessageWriter_CannotWriteTopLevelNull=Cannot write the value 'null' in top level property; return 204 instead. ODataMessageWriter_CannotWriteNullInRawFormat=Cannot write the value 'null' in raw format. ODataMessageWriter_CannotSetHeadersWithInvalidPayloadKind=Cannot set message headers for the invalid payload kind '{0}'. ODataMessageWriter_IncompatiblePayloadKinds=The payload kind '{0}' used in the last call to ODataUtils.SetHeadersForPayload is incompatible with the payload being written, which is of kind '{1}'. diff --git a/src/Microsoft.OData.Core/ODataConstants.cs b/src/Microsoft.OData.Core/ODataConstants.cs index 520c7bc661..e849bd0488 100644 --- a/src/Microsoft.OData.Core/ODataConstants.cs +++ b/src/Microsoft.OData.Core/ODataConstants.cs @@ -176,6 +176,9 @@ internal static class ODataInternalConstants /// The "," used to split properties of Select and Expand fragment a context URI. internal const string ContextUriProjectionPropertySeparator = ","; + /// The token that indicates the payload is a property with null value. + internal const string ContextUriFragmentNull = "Edm.Null"; + /// The token that indicates the payload is a property with an untyped value. internal const string ContextUriFragmentUntyped = "Edm.Untyped"; diff --git a/src/Microsoft.OData.Core/ODataContextUrlInfo.cs b/src/Microsoft.OData.Core/ODataContextUrlInfo.cs index d7a839093c..ef87554f6b 100644 --- a/src/Microsoft.OData.Core/ODataContextUrlInfo.cs +++ b/src/Microsoft.OData.Core/ODataContextUrlInfo.cs @@ -313,6 +313,12 @@ private static string GetTypeNameForValue(ODataValue value, IEdmModel model) return null; } + // special identifier for null values. + if (value.IsNullValue) + { + return ODataConstants.ContextUriFragmentNull; + } + if (value.TypeAnnotation != null && !string.IsNullOrEmpty(value.TypeAnnotation.TypeName)) { return value.TypeAnnotation.TypeName; diff --git a/src/Microsoft.OData.Core/ODataLibraryCompatibility.cs b/src/Microsoft.OData.Core/ODataLibraryCompatibility.cs new file mode 100644 index 0000000000..875db36699 --- /dev/null +++ b/src/Microsoft.OData.Core/ODataLibraryCompatibility.cs @@ -0,0 +1,32 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.OData +{ + /// + /// Library compatibility levels. + /// + [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue")] + public enum ODataLibraryCompatibility + { + /// + /// Version 6.x + /// + Version6 = 060000, + + /// + /// Version 7.x + /// + Version7 = 070000, + + /// + /// The latest version of the library. + /// + Latest = int.MaxValue + } +} diff --git a/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs b/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs index 0e59409302..b168c674ad 100644 --- a/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs +++ b/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs @@ -44,6 +44,7 @@ public ODataMessageReaderSettings(ODataVersion odataVersion) this.EnableMessageStreamDisposal = true; this.EnableCharactersCheck = false; this.Version = odataVersion; + this.LibraryCompatibility = ODataLibraryCompatibility.Latest; Validator = new ReaderValidator(this); if (odataVersion < ODataVersion.V401) @@ -60,6 +61,11 @@ public ODataMessageReaderSettings(ODataVersion odataVersion) } } + /// + /// Gets or sets library compatibility version. Default value is , + /// + public ODataLibraryCompatibility LibraryCompatibility { get; set; } + /// Gets or sets the OData protocol version to be used for reading payloads. /// The OData protocol version to be used for reading payloads. public ODataVersion? Version { get; set; } @@ -257,6 +263,7 @@ private void CopyFrom(ODataMessageReaderSettings other) this.ThrowOnDuplicatePropertyNames = other.ThrowOnDuplicatePropertyNames; this.ThrowIfTypeConflictsWithMetadata = other.ThrowIfTypeConflictsWithMetadata; this.ThrowOnUndeclaredPropertyForNonOpenType = other.ThrowOnUndeclaredPropertyForNonOpenType; + this.LibraryCompatibility = other.LibraryCompatibility; this.Version = other.Version; } } diff --git a/src/Microsoft.OData.Core/ODataMessageWriterSettings.cs b/src/Microsoft.OData.Core/ODataMessageWriterSettings.cs index 731e19399d..6bc368bed2 100644 --- a/src/Microsoft.OData.Core/ODataMessageWriterSettings.cs +++ b/src/Microsoft.OData.Core/ODataMessageWriterSettings.cs @@ -73,8 +73,14 @@ public ODataMessageWriterSettings() this.EnableCharactersCheck = false; this.Validations = ValidationKinds.All; this.Validator = new WriterValidator(this); + this.LibraryCompatibility = ODataLibraryCompatibility.Latest; } + /// + /// Gets or sets library compatibility version. Default value is , + /// + public ODataLibraryCompatibility LibraryCompatibility { get; set; } + /// /// Gets or sets validations to perform. Default value is , /// @@ -398,6 +404,7 @@ private void CopyFrom(ODataMessageWriterSettings other) this.shouldIncludeAnnotation = other.shouldIncludeAnnotation; this.useFormat = other.useFormat; this.Version = other.Version; + this.LibraryCompatibility = other.LibraryCompatibility; this.validations = other.validations; this.ThrowIfTypeConflictsWithMetadata = other.ThrowIfTypeConflictsWithMetadata; diff --git a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs index 84df52e28c..30ac02db3b 100644 --- a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs @@ -500,6 +500,15 @@ internal static string ODataMessageWriter_AsyncInRequest { } } + /// + /// A string like "Cannot write the value 'null' in top level property; return 204 instead." + /// + internal static string ODataMessageWriter_CannotWriteTopLevelNull { + get { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataMessageWriter_CannotWriteTopLevelNull); + } + } + /// /// A string like "Cannot write the value 'null' in raw format." /// diff --git a/src/Microsoft.OData.Core/WriterValidator.cs b/src/Microsoft.OData.Core/WriterValidator.cs index e4a26524b3..6f67d2827b 100644 --- a/src/Microsoft.OData.Core/WriterValidator.cs +++ b/src/Microsoft.OData.Core/WriterValidator.cs @@ -197,14 +197,25 @@ public virtual void ValidateMetadataResource(ODataResourceBase resource, IEdmEnt /// The expected property type or null if we /// don't have any. /// The name of the property. + /// true if the property is top-level. /// The model used to get the OData version. public void ValidateNullPropertyValue(IEdmTypeReference expectedPropertyTypeReference, - string propertyName, IEdmModel model) + string propertyName, bool isTopLevel, IEdmModel model) { if (settings.ThrowIfTypeConflictsWithMetadata) { WriterValidationUtils.ValidateNullPropertyValue(expectedPropertyTypeReference, propertyName, model); } + + if (isTopLevel && this.settings.LibraryCompatibility >= ODataLibraryCompatibility.Version7) + { + // From the spec: + // 11.2.3 Requesting Individual Properties + // ... + // If the property is single-valued and has the null value, the service responds with 204 No Content. + // ... + throw new ODataException(Strings.ODataMessageWriter_CannotWriteTopLevelNull); + } } /// diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightDeserializerTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightDeserializerTests.cs index 2fb85bbbdc..b99f2cf566 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightDeserializerTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightDeserializerTests.cs @@ -781,6 +781,43 @@ public void ParsingTypeDefinitionOverflowValueShouldFail() action.ShouldThrow().WithMessage("Value '123456789' was either too large or too small for a 'NS.UInt16'."); } + #region Top level property + + [Fact] + public void TopLevelPropertyShouldReadProperty() + { + var model = this.CreateEdmModelWithEntity(); + var primitiveTypeRef = ((IEdmEntityType)model.SchemaElements.First()).Properties().First().Type; + this.messageReaderSettings = new ODataMessageReaderSettings(); + ODataJsonLightPropertyAndValueDeserializer deserializer = new ODataJsonLightPropertyAndValueDeserializer(this.CreateJsonLightInputContext("{\"@odata.context\":\"http://odata.org/test/$metadata#Customers(1)/Name\",\"value\":\"Joe\"}", model)); + ODataProperty property = deserializer.ReadTopLevelProperty(primitiveTypeRef); + TestUtils.AssertODataValueAreEqual(new ODataPrimitiveValue("Joe"), property.ODataValue); + } + + [Fact] + public void TopLevelPropertyShouldReadNullProperty() + { + var model = this.CreateEdmModelWithEntity(); + var primitiveTypeRef = ((IEdmEntityType)model.SchemaElements.First()).Properties().First().Type; + this.messageReaderSettings = new ODataMessageReaderSettings(); + ODataJsonLightPropertyAndValueDeserializer deserializer = new ODataJsonLightPropertyAndValueDeserializer(this.CreateJsonLightInputContext("{\"@odata.context\":\"http://odata.org/test/$metadata#Customers(1)/Name\",\"value\":null}", model)); + ODataProperty property = deserializer.ReadTopLevelProperty(primitiveTypeRef); + TestUtils.AssertODataValueAreEqual(new ODataNullValue(), property.ODataValue); + } + + [Fact] + public void TopLevelPropertyShouldRead6xNullProperty() + { + var model = this.CreateEdmModelWithEntity(); + var primitiveTypeRef = ((IEdmEntityType)model.SchemaElements.First()).Properties().First().Type; + this.messageReaderSettings = new ODataMessageReaderSettings(); + ODataJsonLightPropertyAndValueDeserializer deserializer = new ODataJsonLightPropertyAndValueDeserializer(this.CreateJsonLightInputContext("{\"@odata.context\":\"http://odata.org/test/$metadata#Customers(1)/Name\",\"@odata.null\":true}", model)); + ODataProperty property = deserializer.ReadTopLevelProperty(primitiveTypeRef); + TestUtils.AssertODataValueAreEqual(new ODataNullValue(), property.ODataValue); + } + + #endregion + #region Top level property instance annotation [Fact] diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightOutputContextTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightOutputContextTests.cs index 95abb768fd..d4472700e8 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightOutputContextTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightOutputContextTests.cs @@ -5,14 +5,14 @@ //--------------------------------------------------------------------- using System; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Text; using FluentAssertions; -using Microsoft.OData.JsonLight; using Microsoft.OData.Edm; +using Microsoft.OData.JsonLight; using Xunit; +using ODataErrorStrings = Microsoft.OData.Strings; namespace Microsoft.OData.Tests.JsonLight { @@ -33,6 +33,21 @@ public void ShouldBeAbleToWritePropertyResponseWithoutModel() WriteAndValidate(outputContext => outputContext.WriteProperty(property), "{\"@odata.context\":\"http://odata.org/test/$metadata#Edm.Guid\",\"value\":\"00000000-0000-0000-0000-000000000000\"}", writingResponse: true); } + [Fact] + public void ShouldBeAbleToWrite6xNullPropertyResponseWithoutModel() + { + ODataProperty property = new ODataProperty { Name = "Prop", Value = null }; + WriteAndValidate(outputContext => outputContext.WriteProperty(property), "{\"@odata.context\":\"http://odata.org/test/$metadata#Edm.Null\",\"@odata.null\":true}", writingResponse: true, use6x: true); + } + + [Fact] + public void ThrowsOnWriteNullPropertyResponseWithoutModel() + { + ODataProperty property = new ODataProperty { Name = "Prop", Value = null }; + Action test = () => WriteAndValidate(outputContext => outputContext.WriteProperty(property), "", writingResponse: true); + test.ShouldThrow().WithMessage(ODataErrorStrings.ODataMessageWriter_CannotWriteTopLevelNull); + } + [Fact] public void ShouldBeAbleToWritePropertyRequestWithoutModelAsync() { @@ -47,6 +62,21 @@ public void ShouldBeAbleToWritePropertyResponseWithoutModelAsync() WriteAndValidate(outputContext => outputContext.WritePropertyAsync(property).Wait(), "{\"@odata.context\":\"http://odata.org/test/$metadata#Edm.Guid\",\"@odata.type\":\"#Guid\",\"value\":\"00000000-0000-0000-0000-000000000000\"}", writingResponse: false, synchronous: false); } + [Fact] + public void ShouldBeAbleToWrite6xNullPropertyResponseWithoutModelAsync() + { + ODataProperty property = new ODataProperty { Name = "Prop", Value = null }; + WriteAndValidate(outputContext => outputContext.WritePropertyAsync(property).Wait(), "{\"@odata.context\":\"http://odata.org/test/$metadata#Edm.Null\",\"@odata.null\":true}", writingResponse: false, synchronous: false, use6x: true); + } + + [Fact] + public void ThrowsOnWriteNullPropertyResponseWithoutModelAsync() + { + ODataProperty property = new ODataProperty { Name = "Prop", Value = null }; + Action test = () => WriteAndValidate(outputContext => outputContext.WritePropertyAsync(property).Wait(), "", writingResponse: false, synchronous: false); + test.ShouldThrow().WithInnerMessage(ODataErrorStrings.ODataMessageWriter_CannotWriteTopLevelNull); + } + [Fact] public void ShouldBeAbleToWriteInstanceAnnotationsInRequest() { @@ -315,10 +345,15 @@ public void AsyncShouldWriteCountAnnotationForEntityReferenceLinksRequest() #endregion async #endregion WriteEntityReferenceLinks - private static void WriteAndValidate(Action test, string expectedPayload, bool writingResponse = true, bool synchronous = true) + private static void WriteAndValidate( + Action test, + string expectedPayload, + bool writingResponse = true, + bool synchronous = true, + bool use6x = false) { MemoryStream stream = new MemoryStream(); - var outputContext = CreateJsonLightOutputContext(stream, writingResponse, synchronous); + var outputContext = CreateJsonLightOutputContext(stream, writingResponse, synchronous, use6x); test(outputContext); ValidateWrittenPayload(stream, expectedPayload); } @@ -330,7 +365,11 @@ private static void ValidateWrittenPayload(MemoryStream stream, string expectedP payload.Should().Be(expectedPayload); } - private static ODataJsonLightOutputContext CreateJsonLightOutputContext(MemoryStream stream, bool writingResponse = true, bool synchronous = true) + private static ODataJsonLightOutputContext CreateJsonLightOutputContext( + MemoryStream stream, + bool writingResponse = true, + bool synchronous = true, + bool use6x = false) { var messageInfo = new ODataMessageInfo { @@ -345,6 +384,11 @@ private static ODataJsonLightOutputContext CreateJsonLightOutputContext(MemorySt var settings = new ODataMessageWriterSettings { Version = ODataVersion.V4 }; settings.SetServiceDocumentUri(new Uri("http://odata.org/test")); settings.ShouldIncludeAnnotation = ODataUtils.CreateAnnotationFilter("*"); + + if (use6x) + { + settings.LibraryCompatibility = ODataLibraryCompatibility.Version6; + } return new ODataJsonLightOutputContext(messageInfo, settings); } diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataMessageReaderSettingsTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataMessageReaderSettingsTests.cs index 2522e30a5d..4e16e331e9 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataMessageReaderSettingsTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataMessageReaderSettingsTests.cs @@ -39,6 +39,7 @@ public void DefaultValuesTest() Assert.True(1000 == settings.MessageQuotas.MaxOperationsPerChangeset, "MaxOperationsPerChangeset should be int.MaxValue."); Assert.True(100 == settings.MessageQuotas.MaxNestingDepth, "The MaxNestingDepth should be set to 100 by default."); Assert.True(1024 * 1024 == settings.MessageQuotas.MaxReceivedMessageSize, "The MaxMessageSize should be set to 1024 * 1024 by default."); + Assert.True(ODataLibraryCompatibility.Latest == settings.LibraryCompatibility, "The LibraryCompatibility should be set to ODataLibraryCompatibility.Latest by default."); } [Fact] @@ -63,6 +64,7 @@ public void DefaultValuesTest401() Assert.True(1000 == settings.MessageQuotas.MaxOperationsPerChangeset, "MaxOperationsPerChangeset should be int.MaxValue."); Assert.True(100 == settings.MessageQuotas.MaxNestingDepth, "The MaxNestingDepth should be set to 100 by default."); Assert.True(1024 * 1024 == settings.MessageQuotas.MaxReceivedMessageSize, "The MaxMessageSize should be set to 1024 * 1024 by default."); + Assert.True(ODataLibraryCompatibility.Latest == settings.LibraryCompatibility, "The LibraryCompatibility should be set to ODataLibraryCompatibility.Latest by default."); } [Fact] @@ -137,9 +139,11 @@ public void ODataMessageReaderCopyConstructorTest() this.CompareMessageReaderSettings(settings, copyOfSettings); // Compare original and settings created from copy constructor after setting rest of the values - settings.EnableMessageStreamDisposal = false; + settings.EnableMessageStreamDisposal = false; settings.Validations &= ~ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType; - settings.MaxProtocolVersion = ODataVersion.V4; + settings.Version = ODataVersion.V401; + settings.MaxProtocolVersion = ODataVersion.V401; + settings.LibraryCompatibility = ODataLibraryCompatibility.Version6; settings.MessageQuotas.MaxPartsPerBatch = 100; settings.MessageQuotas.MaxOperationsPerChangeset = 200; settings.MessageQuotas.MaxNestingDepth = 20; diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataMessageWriterSettingsTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataMessageWriterSettingsTests.cs index b04826cfb6..4a112fafb0 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataMessageWriterSettingsTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataMessageWriterSettingsTests.cs @@ -118,6 +118,12 @@ public void JsonPCallbackShouldBeNullByDefault() this.settings.JsonPCallback.Should().BeNull(); } + [Fact] + public void LibraryCompatibilityShouldBeLatestByDefault() + { + this.settings.LibraryCompatibility.Should().Be(ODataLibraryCompatibility.Latest); + } + [Fact] public void VersionShouldBeNullByDefault() { @@ -232,6 +238,13 @@ public void CopyConstructorShouldCopyVersion() this.settings.Clone().Version.Should().Be(ODataVersion.V4); } + [Fact] + public void CopyConstructorShouldCopyLibraryCompatibility() + { + this.settings.LibraryCompatibility = ODataLibraryCompatibility.Version6; + this.settings.Clone().LibraryCompatibility.Should().Be(ODataLibraryCompatibility.Version6); + } + [Fact] public void CopyConstructorShouldCopyAnnotationFilter() { @@ -315,6 +328,7 @@ public void PropertyGettersAndSettersTest() const int maxOperationsPerChangeset = 43; const int maxNestingDepth = 44; const ODataVersion version = ODataVersion.V4; + const ODataLibraryCompatibility library = ODataLibraryCompatibility.Version6; this.settings = new ODataMessageWriterSettings() { @@ -329,6 +343,7 @@ public void PropertyGettersAndSettersTest() MaxNestingDepth = maxNestingDepth, }, Version = version, + LibraryCompatibility = library, }; this.settings.ThrowOnDuplicatePropertyNames.Should().BeFalse(); @@ -339,6 +354,7 @@ public void PropertyGettersAndSettersTest() this.settings.MessageQuotas.MaxOperationsPerChangeset.Should().Be(maxOperationsPerChangeset); this.settings.MessageQuotas.MaxNestingDepth.Should().Be(maxNestingDepth); this.settings.Version.Should().Be(version); + this.settings.LibraryCompatibility.Should().Be(library); } [Fact] diff --git a/test/FunctionalTests/Tests/DataOData/Common/OData/JsonLight/AnnotatedPayloadElementToJsonLightConverter.cs b/test/FunctionalTests/Tests/DataOData/Common/OData/JsonLight/AnnotatedPayloadElementToJsonLightConverter.cs index 371ae42a7e..f27dfb9509 100644 --- a/test/FunctionalTests/Tests/DataOData/Common/OData/JsonLight/AnnotatedPayloadElementToJsonLightConverter.cs +++ b/test/FunctionalTests/Tests/DataOData/Common/OData/JsonLight/AnnotatedPayloadElementToJsonLightConverter.cs @@ -253,11 +253,6 @@ public JsonValue Visit(ComplexProperty payloadElement) if (isTopLevel) { - if (payloadElement.Value.IsNull) - { - // TODO: Change the payload of null top-level properties #645 - } - JsonValue complexValue = this.Recurse(payloadElement.Value); // NOTE: top-level complex properties don't use a wrapper object. @@ -644,7 +639,6 @@ public JsonValue Visit(PrimitiveProperty payloadElement) bool needsWrapping = this.CurrentElementIsRoot(); string propertyName = needsWrapping ? JsonLightConstants.ODataValuePropertyName : payloadElement.Name; - // TODO: Change the payload of null top-level properties #645 JsonProperty jsonProperty = new JsonProperty(propertyName, null); jsonProperty.Value = this.Recurse(payloadElement.Value, jsonProperty); diff --git a/test/FunctionalTests/Tests/DataOData/Tests/OData.Common.Tests/PublicApi/PublicApi.bsl b/test/FunctionalTests/Tests/DataOData/Tests/OData.Common.Tests/PublicApi/PublicApi.bsl index 3a5edb6dda..73ed91bdef 100644 --- a/test/FunctionalTests/Tests/DataOData/Tests/OData.Common.Tests/PublicApi/PublicApi.bsl +++ b/test/FunctionalTests/Tests/DataOData/Tests/OData.Common.Tests/PublicApi/PublicApi.bsl @@ -3966,6 +3966,12 @@ public enum Microsoft.OData.ODataDeltaReaderState : int { Start = 0 } +public enum Microsoft.OData.ODataLibraryCompatibility : int { + Latest = 2147483647 + Version6 = 60000 + Version7 = 70000 +} + public enum Microsoft.OData.ODataNullValueBehaviorKind : int { Default = 0 DisableValidation = 2 @@ -5083,6 +5089,7 @@ public sealed class Microsoft.OData.ODataMessageReaderSettings { bool EnableCharactersCheck { public get; public set; } bool EnableMessageStreamDisposal { public get; public set; } bool EnablePrimitiveTypeConversion { public get; public set; } + Microsoft.OData.ODataLibraryCompatibility LibraryCompatibility { public get; public set; } Microsoft.OData.ODataVersion MaxProtocolVersion { public get; public set; } Microsoft.OData.ODataMessageQuotas MessageQuotas { public get; public set; } System.Func`3[[System.Object],[System.String],[Microsoft.OData.Edm.IEdmTypeReference]] PrimitiveTypeResolver { public get; public set; } @@ -5167,6 +5174,7 @@ public sealed class Microsoft.OData.ODataMessageWriterSettings { bool EnableCharactersCheck { public get; public set; } bool EnableMessageStreamDisposal { public get; public set; } string JsonPCallback { public get; public set; } + Microsoft.OData.ODataLibraryCompatibility LibraryCompatibility { public get; public set; } Microsoft.OData.ODataMessageQuotas MessageQuotas { public get; public set; } Microsoft.OData.ODataUri ODataUri { public get; public set; } Microsoft.OData.ValidationKinds Validations { public get; public set; } diff --git a/test/FunctionalTests/Tests/DataOData/Tests/OData.Common.Tests/TestCodeTests/AnnotatedPayloadElementToJsonLightConverterTests.cs b/test/FunctionalTests/Tests/DataOData/Tests/OData.Common.Tests/TestCodeTests/AnnotatedPayloadElementToJsonLightConverterTests.cs index 2462a762e4..5d06facdde 100644 --- a/test/FunctionalTests/Tests/DataOData/Tests/OData.Common.Tests/TestCodeTests/AnnotatedPayloadElementToJsonLightConverterTests.cs +++ b/test/FunctionalTests/Tests/DataOData/Tests/OData.Common.Tests/TestCodeTests/AnnotatedPayloadElementToJsonLightConverterTests.cs @@ -239,7 +239,6 @@ public void JsonLightTaupoSerializerPropertyTest() var testCases = new JsonLightSerializerTestCase[] { // Null property - // TODO: Change the payload of null top-level properties #645 new JsonLightSerializerTestCase { PayloadElement = PayloadBuilder.PrimitiveProperty("Prop", null).WithContextUri("http://odata.org/metadatauri"), diff --git a/test/FunctionalTests/Tests/DataOData/Tests/OData.Reader.Tests/JsonLight/CollectionValueReaderJsonLightTests.cs b/test/FunctionalTests/Tests/DataOData/Tests/OData.Reader.Tests/JsonLight/CollectionValueReaderJsonLightTests.cs index a0569a210e..05c5b26aa7 100644 --- a/test/FunctionalTests/Tests/DataOData/Tests/OData.Reader.Tests/JsonLight/CollectionValueReaderJsonLightTests.cs +++ b/test/FunctionalTests/Tests/DataOData/Tests/OData.Reader.Tests/JsonLight/CollectionValueReaderJsonLightTests.cs @@ -34,7 +34,6 @@ public class CollectionValueReaderJsonLightTests : ODataReaderTestCase public PayloadReaderTestDescriptor.Settings Settings { get; set; } [TestMethod, TestCategory("Reader.Json"), Variation(Description = "Verifies correct reading of collection values")] - // TODO: Change the payload of null top-level properties #645 public void CollectionValueTest() { EdmModel model = new EdmModel(); diff --git a/test/FunctionalTests/Tests/DataOData/Tests/OData.Reader.Tests/JsonLight/NullValueTests.cs b/test/FunctionalTests/Tests/DataOData/Tests/OData.Reader.Tests/JsonLight/NullValueTests.cs index 3d35091e39..b43284e409 100644 --- a/test/FunctionalTests/Tests/DataOData/Tests/OData.Reader.Tests/JsonLight/NullValueTests.cs +++ b/test/FunctionalTests/Tests/DataOData/Tests/OData.Reader.Tests/JsonLight/NullValueTests.cs @@ -29,7 +29,6 @@ public class NullValueJsonLightTests : ODataReaderTestCase public PayloadReaderTestDescriptor.Settings Settings { get; set; } [TestMethod, TestCategory("Reader.Json"), Variation(Description = "Verifies correct reading of null property values")] - // TODO: Change the payload of null top-level properties #645 public void NullValuePropertyTests() { EdmModel model = new EdmModel(); diff --git a/test/FunctionalTests/Tests/DataOData/Tests/OData.Reader.Tests/JsonLight/PropertyReaderJsonLightTests.cs b/test/FunctionalTests/Tests/DataOData/Tests/OData.Reader.Tests/JsonLight/PropertyReaderJsonLightTests.cs index 3423bc34d5..227be12899 100644 --- a/test/FunctionalTests/Tests/DataOData/Tests/OData.Reader.Tests/JsonLight/PropertyReaderJsonLightTests.cs +++ b/test/FunctionalTests/Tests/DataOData/Tests/OData.Reader.Tests/JsonLight/PropertyReaderJsonLightTests.cs @@ -40,7 +40,6 @@ public class PropertyReaderJsonLightTests : ODataReaderTestCase public EntityModelSchemaToEdmModelConverter EntityModelSchemaToEdmModelConverter { get; set; } [TestMethod, TestCategory("Reader.Json"), Variation(Description = "Verifies correct reading of top-level property payloads")] - // TODO: Change the payload of null top-level properties #645 public void TopLevelPropertyTest() { var injectedProperties = new[] @@ -329,7 +328,6 @@ public void TopLevelPropertyTest() } [TestMethod, TestCategory("Reader.Json"), Variation(Description = "Verifies correct reading of top-level open properties.")] - // TODO: Change the payload of null top-level properties #645 public void OpenTopLevelPropertiesTest() { IEdmModel model = TestModels.BuildTestModel(); diff --git a/test/FunctionalTests/Tests/DataOData/Tests/OData.Reader.Tests/Reader/PropertyReaderTests.cs b/test/FunctionalTests/Tests/DataOData/Tests/OData.Reader.Tests/Reader/PropertyReaderTests.cs index 49227eb6c5..d0a69cb1d7 100644 --- a/test/FunctionalTests/Tests/DataOData/Tests/OData.Reader.Tests/Reader/PropertyReaderTests.cs +++ b/test/FunctionalTests/Tests/DataOData/Tests/OData.Reader.Tests/Reader/PropertyReaderTests.cs @@ -163,7 +163,6 @@ private sealed class NonNullablePropertyTest } [TestMethod, TestCategory("Reader.Properties"), Variation(Description = "Test reading of non-nullable property payloads with 'null' value and metadata.")] - // TODO: Change the payload of null top-level properties #645 public void NonNullablePropertiesWithNullValuesTest() { IEnumerable testCases = new NonNullablePropertyTest[] diff --git a/test/FunctionalTests/Tests/DataServices/UnitTests/ServerUnitTests/Utils/PayloadGenerator.cs b/test/FunctionalTests/Tests/DataServices/UnitTests/ServerUnitTests/Utils/PayloadGenerator.cs index 14d8d2e0fb..f26a8b3c51 100644 --- a/test/FunctionalTests/Tests/DataServices/UnitTests/ServerUnitTests/Utils/PayloadGenerator.cs +++ b/test/FunctionalTests/Tests/DataServices/UnitTests/ServerUnitTests/Utils/PayloadGenerator.cs @@ -695,7 +695,6 @@ private void GeneratePropertyPayload(PayloadBuilder builder) } } - // TODO: Change the payload of null top-level properties #645 if (property.PropertyKind == PayloadBuilderPropertyKind.Primitive || property.Value == null) { // Write primitive property value diff --git a/test/FunctionalTests/Tests/DataServices/UnitTests/ServerUnitTests1/Tests/UpdatePropertyTests.cs b/test/FunctionalTests/Tests/DataServices/UnitTests/ServerUnitTests1/Tests/UpdatePropertyTests.cs index 3a385e7d2c..3b6eecf876 100644 --- a/test/FunctionalTests/Tests/DataServices/UnitTests/ServerUnitTests1/Tests/UpdatePropertyTests.cs +++ b/test/FunctionalTests/Tests/DataServices/UnitTests/ServerUnitTests1/Tests/UpdatePropertyTests.cs @@ -709,7 +709,6 @@ public void UpdatePutComplexPropertyToNull() } [Ignore] - // TODO: Change the payload of null top-level properties #645 // [TestCategory("Partition2"), TestMethod, Variation] public void UpdateMergePrimitivePropertyToNull() { diff --git a/test/FunctionalTests/Tests/DataServices/UnitTests/ServerUnitTests2/Tests/NonNullablePropertiesTests.cs b/test/FunctionalTests/Tests/DataServices/UnitTests/ServerUnitTests2/Tests/NonNullablePropertiesTests.cs index aac7b2be29..b49a0ab9ad 100644 --- a/test/FunctionalTests/Tests/DataServices/UnitTests/ServerUnitTests2/Tests/NonNullablePropertiesTests.cs +++ b/test/FunctionalTests/Tests/DataServices/UnitTests/ServerUnitTests2/Tests/NonNullablePropertiesTests.cs @@ -149,7 +149,6 @@ public void NonNullableTopLevelPropertiesTest() if (requestMDSV != null) request.RequestMaxVersion = requestMDSV.ToString(); request.RequestContentType = format; - // TODO: Change the payload of null top-level properties #645 request.SetRequestStreamAsText(@"{ ""value"" : null }"); IDisposable dispose = null;