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

Add the writing derived type constraint validation #1358

Merged
merged 4 commits into from
Dec 18, 2018
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
12 changes: 12 additions & 0 deletions src/Microsoft.OData.Core/IWriterValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace Microsoft.OData
{
using System.Collections.Generic;
using Microsoft.OData.Edm;

/// <summary>
Expand Down Expand Up @@ -130,5 +131,16 @@ IEdmNavigationProperty ValidateNestedResourceInfo(
ODataNestedResourceInfo nestedResourceInfo,
IEdmStructuredType declaringStructuredType,
ODataPayloadKind? expandedPayloadKind);

/// <summary>
/// Validates the input <see cref="IEdmStructuredType"/> meets the derived type constaints on the <see cref="IEdmNavigationSource"/>.
/// </summary>
/// <param name="resourceType">The input resource type.</param>
/// <param name="metadataType">The type from metadata.</param>
/// <param name="derivedTypeConstraints">The derived type constraints on the nested resource.</param>
/// <param name="itemKind">The item kind.</param>
/// <param name="itemName">The item name.</param>
void ValidateDerivedTypeConstraint(IEdmStructuredType resourceType,
IEdmStructuredType metadataType, IEnumerable<string> derivedTypeConstraints, string itemKind, string itemName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,11 @@ private void WriteProperty(

if (!this.JsonLightOutputContext.PropertyCacheHandler.InResourceSetScope())
{
this.currentPropertyInfo = new PropertySerializationInfo(propertyName, owningType) { IsTopLevel = isTopLevel };
this.currentPropertyInfo = new PropertySerializationInfo(this.JsonLightOutputContext.Model, propertyName, owningType) { IsTopLevel = isTopLevel };
}
else
{
this.currentPropertyInfo = this.JsonLightOutputContext.PropertyCacheHandler.GetProperty(propertyName, owningType);
this.currentPropertyInfo = this.JsonLightOutputContext.PropertyCacheHandler.GetProperty(this.JsonLightOutputContext.Model, propertyName, owningType);
}

WriterValidationUtils.ValidatePropertyDefined(this.currentPropertyInfo, this.MessageWriterSettings.ThrowOnUndeclaredPropertyForNonOpenType);
Expand Down Expand Up @@ -496,6 +496,8 @@ private void WritePrimitiveProperty(
{
ResolvePrimitiveValueTypeName(primitiveValue, isOpenPropertyType);

WriterValidationUtils.ValidatePropertyDerivedTypeConstraint(this.currentPropertyInfo);

this.WritePropertyTypeName();
this.JsonWriter.WriteName(this.currentPropertyInfo.WireName);
this.JsonLightValueSerializer.WritePrimitiveValue(primitiveValue.Value, this.currentPropertyInfo.ValueType.TypeReference, this.currentPropertyInfo.MetadataType.TypeReference);
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.OData.Core/JsonLight/ODataJsonLightWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1951,6 +1951,7 @@ internal override NestedResourceInfoScope Clone(WriterState newWriterState)
{
EntityReferenceLinkWritten = this.entityReferenceLinkWritten,
ResourceSetWritten = this.resourceSetWritten,
DerivedTypeConstraints = this.DerivedTypeConstraints
};
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.OData.Core/Microsoft.OData.Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ internal sealed class TextRes {
internal const string WriterValidationUtils_NavigationLinkMustSpecifyUrl = "WriterValidationUtils_NavigationLinkMustSpecifyUrl";
internal const string WriterValidationUtils_NestedResourceInfoMustSpecifyIsCollection = "WriterValidationUtils_NestedResourceInfoMustSpecifyIsCollection";
internal const string WriterValidationUtils_MessageWriterSettingsJsonPaddingOnRequestMessage = "WriterValidationUtils_MessageWriterSettingsJsonPaddingOnRequestMessage";
internal const string WriterValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint = "WriterValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint";
internal const string XmlReaderExtension_InvalidNodeInStringValue = "XmlReaderExtension_InvalidNodeInStringValue";
internal const string XmlReaderExtension_InvalidRootNode = "XmlReaderExtension_InvalidRootNode";
internal const string ODataMetadataInputContext_ErrorReadingMetadata = "ODataMetadataInputContext_ErrorReadingMetadata";
Expand Down Expand Up @@ -654,7 +655,6 @@ internal sealed class TextRes {
internal const string MetadataBinder_BinaryOperatorOperandNotSingleValue = "MetadataBinder_BinaryOperatorOperandNotSingleValue";
internal const string MetadataBinder_UnaryOperatorOperandNotSingleValue = "MetadataBinder_UnaryOperatorOperandNotSingleValue";
internal const string MetadataBinder_LeftOperandNotSingleValue = "MetadataBinder_LeftOperandNotSingleValue";
internal const string StringItemShouldBeQuoted = "StringItemShouldBeQuoted";
internal const string MetadataBinder_RightOperandNotCollectionValue = "MetadataBinder_RightOperandNotCollectionValue";
internal const string MetadataBinder_PropertyAccessSourceNotSingleValue = "MetadataBinder_PropertyAccessSourceNotSingleValue";
internal const string MetadataBinder_IncompatibleOperandsError = "MetadataBinder_IncompatibleOperandsError";
Expand All @@ -678,6 +678,7 @@ internal sealed class TextRes {
internal const string MetadataBinder_CollectionOpenPropertiesNotSupportedInThisRelease = "MetadataBinder_CollectionOpenPropertiesNotSupportedInThisRelease";
internal const string MetadataBinder_IllegalSegmentType = "MetadataBinder_IllegalSegmentType";
internal const string MetadataBinder_QueryOptionNotApplicable = "MetadataBinder_QueryOptionNotApplicable";
internal const string StringItemShouldBeQuoted = "StringItemShouldBeQuoted";
internal const string ApplyBinder_AggregateExpressionIncompatibleTypeForMethod = "ApplyBinder_AggregateExpressionIncompatibleTypeForMethod";
internal const string ApplyBinder_UnsupportedAggregateMethod = "ApplyBinder_UnsupportedAggregateMethod";
internal const string ApplyBinder_UnsupportedAggregateKind = "ApplyBinder_UnsupportedAggregateKind";
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.OData.Core/Microsoft.OData.Core.txt
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ WriterValidationUtils_MessageWriterSettingsServiceDocumentUriMustBeNullOrAbsolut
WriterValidationUtils_NavigationLinkMustSpecifyUrl=The ODataNestedResourceInfo.Url property on an navigation link '{0}' is null. The ODataNestedResourceInfo.Url property must be set to a non-null value that represents the entity or entities the navigation link references.
WriterValidationUtils_NestedResourceInfoMustSpecifyIsCollection=The ODataNestedResourceInfo.IsCollection property on a nested resource info '{0}' is null. The ODataNestedResourceInfo.IsCollection property must be specified when writing a nested resource into a request.
WriterValidationUtils_MessageWriterSettingsJsonPaddingOnRequestMessage=A JSON Padding function was specified on ODataMessageWriterSettings when trying to write a request message. JSON Padding is only for writing responses.
WriterValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint=The value type '{0}' is not allowed due to an Org.OData.Validation.V1.DerivedTypeConstraint annotation on {1} '{2}'.

XmlReaderExtension_InvalidNodeInStringValue=An XML node of type '{0}' was found in a string value. An element with a string value can only contain Text, CDATA, SignificantWhitespace, Whitespace or Comment nodes.
XmlReaderExtension_InvalidRootNode=An XML node of type '{0}' was found at the root level. The root level of an OData payload must contain a single XML element and no text nodes.
Expand Down
42 changes: 38 additions & 4 deletions src/Microsoft.OData.Core/ODataWriterCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ protected ODataWriterCore(
this.listener = listener;

this.scopeStack.Push(new Scope(WriterState.Start, /*item*/null, navigationSource, resourceType, /*skipWriting*/false, outputContext.MessageWriterSettings.SelectedProperties, odataUri));
this.CurrentScope.DerivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationSource);
}

/// <summary>
Expand Down Expand Up @@ -1726,6 +1727,9 @@ private void ValidateResourceForResourceSet(ODataResourceBase resource, Resource
// Validate the consistency of resource types in the nested resourceSet/resource
this.WriterValidator.ValidateResourceInNestedResourceInfo(resourceType, parentNestedResourceInfoScope.ResourceType);
resourceScope.ResourceTypeFromMetadata = parentNestedResourceInfoScope.ResourceType;

this.WriterValidator.ValidateDerivedTypeConstraint(resourceType, resourceScope.ResourceTypeFromMetadata,
parentNestedResourceInfoScope.DerivedTypeConstraints, "property", ((ODataNestedResourceInfo)parentNestedResourceInfoScope.Item).Name);
}
else
{
Expand All @@ -1750,6 +1754,12 @@ private void ValidateResourceForResourceSet(ODataResourceBase resource, Resource
this.CurrentResourceSetValidator.ValidateResource(resourceType);
}
}

if (this.ParentScope.NavigationSource != null)
{
this.WriterValidator.ValidateDerivedTypeConstraint(resourceType, resourceScope.ResourceTypeFromMetadata,
this.ParentScope.DerivedTypeConstraints, "navigation source", this.ParentScope.NavigationSource.Name);
}
}

resourceScope.ResourceType = resourceType;
Expand Down Expand Up @@ -1875,6 +1885,8 @@ private void EnterScope(WriterState newState, ODataItem item)
odataUri.Path = new ODataPath();
}

IEnumerable<string> derivedTypeConstraints = null;

WriterState currentState = currentScope.State;

if (newState == WriterState.Resource || newState == WriterState.ResourceSet || newState == WriterState.Primitive || newState == WriterState.DeltaResourceSet || newState == WriterState.DeletedResource)
Expand Down Expand Up @@ -1944,6 +1956,15 @@ private void EnterScope(WriterState newState, ODataItem item)
}
}

if (navigationSource == null)
{
derivedTypeConstraints = currentScope.DerivedTypeConstraints;
}
else
{
derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationSource);
}

navigationSource = navigationSource ?? currentScope.NavigationSource;
resourceType = resourceType ?? currentScope.ResourceType;

Expand Down Expand Up @@ -2011,12 +2032,16 @@ private void EnterScope(WriterState newState, ODataItem item)
}

odataPath = odataPath.AppendPropertySegment(structuredProperty);

derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(structuredProperty);
}
else
{
IEdmNavigationProperty navigationProperty = this.WriterValidator.ValidateNestedResourceInfo(nestedResourceInfo, currentResourceType, /*payloadKind*/null);
if (navigationProperty != null)
{
derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationProperty);

resourceType = navigationProperty.ToEntityType();
if (!nestedResourceInfo.IsCollection.HasValue)
{
Expand Down Expand Up @@ -2095,7 +2120,7 @@ private void EnterScope(WriterState newState, ODataItem item)
navigationSource = this.CurrentScope.NavigationSource ?? odataUri.Path.TargetNavigationSource();
}

this.PushScope(newState, item, navigationSource, resourceType, skipWriting, selectedProperties, odataUri);
this.PushScope(newState, item, navigationSource, resourceType, skipWriting, selectedProperties, odataUri, derivedTypeConstraints);

this.NotifyListener(newState);
}
Expand Down Expand Up @@ -2150,7 +2175,7 @@ private void LeaveScope()
{
Scope startScope = this.scopeStack.Pop();
Debug.Assert(startScope.State == WriterState.Start, "startScope.State == WriterState.Start");
this.PushScope(WriterState.Completed, /*item*/null, startScope.NavigationSource, startScope.ResourceType, /*skipWriting*/false, startScope.SelectedProperties, startScope.ODataUri);
this.PushScope(WriterState.Completed, /*item*/null, startScope.NavigationSource, startScope.ResourceType, /*skipWriting*/false, startScope.SelectedProperties, startScope.ODataUri, null);
this.InterceptException(this.EndPayload);
this.NotifyListener(WriterState.Completed);
}
Expand Down Expand Up @@ -2297,8 +2322,10 @@ private void ValidateTransition(WriterState newState)
/// <param name="skipWriting">true if the content of the scope to create should not be written.</param>
/// <param name="selectedProperties">The selected properties of this scope.</param>
/// <param name="odataUri">The OdataUri info of this scope.</param>
/// <param name="derivedTypeConstraints">The derived type constraints.</param>
[SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Debug.Assert check only.")]
private void PushScope(WriterState state, ODataItem item, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
private void PushScope(WriterState state, ODataItem item, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri,
IEnumerable<string> derivedTypeConstraints)
{
Debug.Assert(
state == WriterState.Error ||
Expand Down Expand Up @@ -2369,6 +2396,7 @@ private void PushScope(WriterState state, ODataItem item, IEdmNavigationSource n
throw new ODataException(errorMessage);
}

scope.DerivedTypeConstraints = derivedTypeConstraints;
this.scopeStack.Push(scope);
}

Expand Down Expand Up @@ -2628,6 +2656,9 @@ internal bool SkipWriting
return this.skipWriting;
}
}

/// <summary>Gets or sets the derived type constraints for the current scope.</summary>
internal IEnumerable<string> DerivedTypeConstraints { get; set; }
}

/// <summary>
Expand Down Expand Up @@ -3038,7 +3069,10 @@ internal NestedResourceInfoScope(WriterState writerState, ODataNestedResourceInf
/// <returns>The cloned nested resource info scope with the specified writer state.</returns>
internal virtual NestedResourceInfoScope Clone(WriterState newWriterState)
{
return new NestedResourceInfoScope(newWriterState, (ODataNestedResourceInfo)this.Item, this.NavigationSource, this.ResourceType, this.SkipWriting, this.SelectedProperties, this.ODataUri);
return new NestedResourceInfoScope(newWriterState, (ODataNestedResourceInfo)this.Item, this.NavigationSource, this.ResourceType, this.SkipWriting, this.SelectedProperties, this.ODataUri)
{
DerivedTypeConstraints = this.DerivedTypeConstraints
};
}
}
}
Expand Down
21 changes: 14 additions & 7 deletions src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2824,6 +2824,13 @@ internal static string WriterValidationUtils_MessageWriterSettingsJsonPaddingOnR
}
}

/// <summary>
/// A string like "The value type '{0}' is not allowed due to an Org.OData.Validation.V1.DerivedTypeConstraint annotation on {1} '{2}'."
/// </summary>
internal static string WriterValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint(object p0, object p1, object p2) {
return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.WriterValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint, p0, p1, p2);
}

/// <summary>
/// A string like "An XML node of type '{0}' was found in a string value. An element with a string value can only contain Text, CDATA, SignificantWhitespace, Whitespace or Comment nodes."
/// </summary>
Expand Down Expand Up @@ -4496,13 +4503,6 @@ internal static string MetadataBinder_LeftOperandNotSingleValue {
}
}

/// <summary>
/// A string like "String item should be single/double quoted: '{0}'."
/// </summary>
internal static string StringItemShouldBeQuoted(object p0) {
return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.StringItemShouldBeQuoted, p0);
}

/// <summary>
/// A string like "The right operand for the IN operation is not a collection value. IN operations require the left operand to be a single value and the right operand to be a collection value."
/// </summary>
Expand Down Expand Up @@ -4682,6 +4682,13 @@ internal static string MetadataBinder_QueryOptionNotApplicable(object p0) {
return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.MetadataBinder_QueryOptionNotApplicable, p0);
}

/// <summary>
/// A string like "String item should be single/double quoted: '{0}'."
/// </summary>
internal static string StringItemShouldBeQuoted(object p0) {
return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.StringItemShouldBeQuoted, p0);
}

/// <summary>
/// A string like "$apply/aggregate expression '{0}' operation does not support value type '{1}'."
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.OData.Core/PropertyCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ internal class PropertyCache
{
private readonly Dictionary<string, PropertySerializationInfo> propertyInfoDictionary = new Dictionary<string, PropertySerializationInfo>();

public PropertySerializationInfo GetProperty(string name, string uniqueName, IEdmStructuredType owningType)
public PropertySerializationInfo GetProperty(IEdmModel model, string name, string uniqueName, IEdmStructuredType owningType)
{
PropertySerializationInfo propertyInfo;
if (!propertyInfoDictionary.TryGetValue(uniqueName, out propertyInfo))
{
propertyInfo = new PropertySerializationInfo(name, owningType);
propertyInfo = new PropertySerializationInfo(model, name, owningType);
propertyInfoDictionary[uniqueName] = propertyInfo;
}

Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.OData.Core/PropertyCacheHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ internal class PropertyCacheHandler

private int currentResourceScopeLevel;

public PropertySerializationInfo GetProperty(string name, IEdmStructuredType owningType)
public PropertySerializationInfo GetProperty(IEdmModel model, string name, IEdmStructuredType owningType)
{
int depth = this.currentResourceScopeLevel - this.resourceSetScopeLevel;

Expand All @@ -45,7 +45,7 @@ public PropertySerializationInfo GetProperty(string name, IEdmStructuredType own
? string.Concat(owningType.FullTypeName(), PropertyCacheHandler.PropertyTypeDelimiter, depthStr, name)
: string.Concat(depthStr, name);

return this.currentPropertyCache.GetProperty(name, uniqueName, owningType);
return this.currentPropertyCache.GetProperty(model, name, uniqueName, owningType);
}

public void SetCurrentResourceScopeLevel(int level)
Expand Down
Loading