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

Validate that a recursive complex-typed property must be nullable #2558

Merged
merged 16 commits into from
Dec 5, 2022
Merged
1 change: 1 addition & 0 deletions src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ internal sealed class EdmRes {
internal const string EdmModel_Validator_Semantic_AmbiguousType = "EdmModel_Validator_Semantic_AmbiguousType";
internal const string EdmModel_Validator_Semantic_InvalidNavigationPropertyType = "EdmModel_Validator_Semantic_InvalidNavigationPropertyType";
internal const string EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentTargetMustBeOptional = "EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentTargetMustBeOptional";
internal const string EdmModel_Validator_Semantic_RecursiveComplexTypedPropertyMustBeOptional = "EdmModel_Validator_Semantic_RecursiveComplexTypedPropertyMustBeOptional";
internal const string EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentSourceMustBeFromZeroOrOne = "EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentSourceMustBeFromZeroOrOne";
internal const string EdmModel_Validator_Semantic_NavigationPropertyWithNonRecursiveContainmentSourceMustBeFromOne = "EdmModel_Validator_Semantic_NavigationPropertyWithNonRecursiveContainmentSourceMustBeFromOne";
internal const string EdmModel_Validator_Semantic_ComplexTypeMustHaveProperties = "EdmModel_Validator_Semantic_ComplexTypeMustHaveProperties";
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.OData.Edm/Microsoft.OData.Edm.txt
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ EdmModel_Validator_Semantic_InaccessibleType=The named type '{0}' could not be f
EdmModel_Validator_Semantic_AmbiguousType=The named type '{0}' is ambiguous from the model being validated.
EdmModel_Validator_Semantic_InvalidNavigationPropertyType=The type of the navigation property '{0}' is invalid. The navigation target type must be an entity type or a collection of entity type. The navigation target entity type must match the declaring type of the partner property.
EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentTargetMustBeOptional=The target multiplicity of the navigation property '{0}' is invalid. If a navigation property has 'ContainsTarget' set to true and declaring entity type of the property is the same or inherits from the target entity type, then the property represents a recursive containment and it must have an optional target represented by a collection or a nullable entity type.
EdmModel_Validator_Semantic_RecursiveComplexTypedPropertyMustBeOptional=The nullability of the property '{0}' is invalid. If a complex typed property is of the same type (or base type) as its declaring type, then the property represents a recursive containment and the given property must be optional.
EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentSourceMustBeFromZeroOrOne=The source multiplicity of the navigation property '{0}' is invalid. If a navigation property has 'ContainsTarget' set to true and declaring entity type of the property is the same or inherits from the target entity type, then the property represents a recursive containment and the multiplicity of the navigation source must be zero or one.
EdmModel_Validator_Semantic_NavigationPropertyWithNonRecursiveContainmentSourceMustBeFromOne=The source multiplicity of the navigation property '{0}' is invalid. If a navigation property has 'ContainsTarget' set to true and declaring entity type of the property is not the same as the target entity type, then the property represents a non-recursive containment and the multiplicity of the navigation source must be exactly one.
EdmModel_Validator_Semantic_ComplexTypeMustHaveProperties=The complex type '{0}' is invalid. A complex type must contain at least one property.
Expand Down
8 changes: 8 additions & 0 deletions src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,14 @@ internal static string EdmModel_Validator_Semantic_NavigationPropertyWithRecursi
return Microsoft.OData.Edm.EdmRes.GetString(Microsoft.OData.Edm.EdmRes.EdmModel_Validator_Semantic_NavigationPropertyWithRecursiveContainmentTargetMustBeOptional, p0);
}

/// <summary>
/// A string like "The nullability of the property '{0}' is invalid. If a complex typed property is of the same type (or base type) as its declaring type, then the property represents a recursive containment and the given property must be optional."
/// </summary>
internal static string EdmModel_Validator_Semantic_RecursiveComplexTypedPropertyMustBeOptional(object p0)
{
return Microsoft.OData.Edm.EdmRes.GetString(Microsoft.OData.Edm.EdmRes.EdmModel_Validator_Semantic_RecursiveComplexTypedPropertyMustBeOptional, p0);
}

/// <summary>
/// A string like "The source multiplicity of the navigation property '{0}' is invalid. If a navigation property has 'ContainsTarget' set to true and declaring entity type of the property is the same or inherits from the target entity type, then the property represents a recursive containment and the multiplicity of the navigation source must be zero or one."
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/Microsoft.OData.Edm/Validation/EdmErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1432,5 +1432,10 @@ public enum EdmErrorCode
/// A property is required in an object
/// </summary>
MissingRequiredProperty,

/// <summary>
/// A recursive complex-typed property must be optional.
/// </summary>
RecursiveComplexTypedPropertyMustBeOptional,
}
}
31 changes: 31 additions & 0 deletions src/Microsoft.OData.Edm/Validation/ValidationRules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1192,6 +1192,37 @@ public static class ValidationRules
}
});

// Add this to rule set as a breaking change for a future release.
/// <summary>
/// Validates that if a complex-typed property has a type or base type that is the same as
corranrogue9 marked this conversation as resolved.
Show resolved Hide resolved
/// the complex type, then the complex type is nullable.
/// </summary>
public static readonly ValidationRule<IEdmStructuralProperty> RecursiveComplexTypedPropertyMustBeOptional =
corranrogue9 marked this conversation as resolved.
Show resolved Hide resolved
new ValidationRule<IEdmStructuralProperty>(
(context, property) =>
{
IEdmTypeReference currentPropTypeRef = property.Type;
IEdmType currentPropType = currentPropTypeRef.Definition;
IEdmStructuredType declaringType = property.DeclaringType;

if (!currentPropTypeRef.IsNullable)
{
while (currentPropType != null && currentPropType != declaringType)
{
IEdmStructuredType baseType = ((IEdmStructuredType)currentPropType).BaseType;
currentPropType = baseType;
}

if (currentPropType == declaringType)
lisicase marked this conversation as resolved.
Show resolved Hide resolved
{
context.AddError(
property.Location(),
EdmErrorCode.RecursiveComplexTypedPropertyMustBeOptional,
Strings.EdmModel_Validator_Semantic_RecursiveComplexTypedPropertyMustBeOptional(property.Name));
}
}
});

#endregion

#region IEdmNavigationProperty
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public void NewValidationRulesShouldBeInTheRuleSetExceptBaselinedExceptionRules(
f.Name != "NavigationPropertyEntityMustNotIndirectlyContainItself" &&
f.Name != "EntityTypeKeyMissingOnEntityType" &&
f.Name != "VocabularyAnnotationTargetAllowedApplyToElement" &&
f.Name != "EntityTypeInvalidKeyKeyDefinedInAncestor")
f.Name != "EntityTypeInvalidKeyKeyDefinedInAncestor" &&
f.Name != nameof(ValidationRules.RecursiveComplexTypedPropertyMustBeOptional))
.Select(f=> new KeyValuePair<object, string>(f.GetValue(null), f.Name));
foreach (var item in items)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,139 @@ public void SameFunctionsShouldInWithOneInReferencedModelShouldRaiseError()
}
#endregion

#region RecursiveComplexTypedPropertyMustBeOptional Tests

[Fact]
public void ComplexTypedPropertyWithSameTypeAsDeclaringTypeShouldError()
{
EdmComplexType complexType = new EdmComplexType("ns", "myType");
IEdmTypeReference complexTypeRef = complexType.GetTypeReference(false);
IEdmStructuralProperty nestedProp = complexType.AddStructuralProperty("nested", complexTypeRef);

ValidateError(
ValidationRules.RecursiveComplexTypedPropertyMustBeOptional,
nestedProp,
EdmErrorCode.RecursiveComplexTypedPropertyMustBeOptional,
Strings.EdmModel_Validator_Semantic_RecursiveComplexTypedPropertyMustBeOptional("nested"));
}

[Fact]
public void ComplexTypedPropertyWithSameBaseTypeAsDeclaringTypeShouldError()
{
EdmComplexType baseType = new EdmComplexType("ns", "myType");
EdmComplexType derivedType = new EdmComplexType("ns", "derivedType", baseType);
IEdmTypeReference derivedTypeRef = derivedType.GetTypeReference(false);
IEdmStructuralProperty nestedBaseProp = baseType.AddStructuralProperty("nested", derivedTypeRef);

ValidateError(
ValidationRules.RecursiveComplexTypedPropertyMustBeOptional,
nestedBaseProp,
EdmErrorCode.RecursiveComplexTypedPropertyMustBeOptional,
Strings.EdmModel_Validator_Semantic_RecursiveComplexTypedPropertyMustBeOptional("nested"));
}

[Fact]
public void ComplexTypedPropertyWithSameTypeAsDeclaringBaseTypeShouldNotError()
{
EdmComplexType baseType = new EdmComplexType("ns", "myType");
EdmComplexType derivedType = new EdmComplexType("ns", "derivedType", baseType);
IEdmTypeReference baseTypeReference = baseType.GetTypeReference(false);
IEdmStructuralProperty nestedDerivedProp = derivedType.AddStructuralProperty("nested", baseTypeReference);

var model = new EdmModel();
model.AddElement(baseType);
model.AddElement(derivedType);

ValidateNoError(
ValidationRules.RecursiveComplexTypedPropertyMustBeOptional,
model,
nestedDerivedProp);
}

[Fact]
public void ComplexTypedPropertyWithSameInheritedBaseTypeAsDeclaringTypeShouldError()
{
EdmComplexType baseType = new EdmComplexType("ns", "myType");
EdmComplexType derivedType1 = new EdmComplexType("ns", "derivedType1", baseType);
EdmComplexType derivedType2 = new EdmComplexType("ns", "derivedType2", derivedType1);
IEdmTypeReference derivedType2Ref = derivedType2.GetTypeReference(false);
IEdmStructuralProperty nestedBaseProp = baseType.AddStructuralProperty("nested", derivedType2Ref);

ValidateError(
ValidationRules.RecursiveComplexTypedPropertyMustBeOptional,
nestedBaseProp,
EdmErrorCode.RecursiveComplexTypedPropertyMustBeOptional,
Strings.EdmModel_Validator_Semantic_RecursiveComplexTypedPropertyMustBeOptional("nested"));
}

[Fact]
public void ComplexTypedPropertyWithSameSecondInheritedBaseTypeAsDeclaringTypeShouldError()
{
EdmComplexType baseType = new EdmComplexType("ns", "myType");
EdmComplexType derivedType1 = new EdmComplexType("ns", "derivedType1", baseType);
EdmComplexType derivedType2 = new EdmComplexType("ns", "derivedType2", derivedType1);
EdmComplexType derivedType3 = new EdmComplexType("ns", "derivedType3", derivedType2);
IEdmTypeReference derivedType3Ref = derivedType3.GetTypeReference(false);
IEdmStructuralProperty nestedBaseProp = baseType.AddStructuralProperty("nested", derivedType3Ref);

ValidateError(
ValidationRules.RecursiveComplexTypedPropertyMustBeOptional,
nestedBaseProp,
EdmErrorCode.RecursiveComplexTypedPropertyMustBeOptional,
Strings.EdmModel_Validator_Semantic_RecursiveComplexTypedPropertyMustBeOptional("nested"));
}

[Fact]
public void ComplexTypedPropertyWithMutualBaseTypeShouldNotError()
{
EdmComplexType baseType = new EdmComplexType("ns", "myType");
EdmComplexType derivedType = new EdmComplexType("ns", "derivedType", baseType);
EdmComplexType otherDerivedType = new EdmComplexType("ns", "otherDerivedType", baseType);
IEdmTypeReference derivedTypeRef = derivedType.GetTypeReference(false);
IEdmStructuralProperty nestedOtherDerivedProp = otherDerivedType.AddStructuralProperty("nested", derivedTypeRef);

var model = new EdmModel();
model.AddElement(baseType);
model.AddElement(derivedType);
model.AddElement(otherDerivedType);

ValidateNoError(
ValidationRules.RecursiveComplexTypedPropertyMustBeOptional,
model,
nestedOtherDerivedProp);
}

[Fact]
public void MultipleComplexTypedPropertiesWithSameTypeAsDeclaringTypeShouldAllError()
{
EdmComplexType complexType = new EdmComplexType("ns", "myType");
IEdmTypeReference complexTypeRef = complexType.GetTypeReference(false);
complexType.AddStructuralProperty("nested1", complexTypeRef);
complexType.AddStructuralProperty("nested2", complexTypeRef);

var model = new EdmModel();
model.AddElement(complexType);
IEnumerable<EdmError> errors;
List<ValidationRule> rules = new List<ValidationRule>
{
ValidationRules.RecursiveComplexTypedPropertyMustBeOptional
};

model.Validate(new ValidationRuleSet(rules), out errors);

Assert.Equal(2, errors.Count());

int currentIndex = 1;
foreach (var error in errors)
{
Assert.Equal(EdmErrorCode.RecursiveComplexTypedPropertyMustBeOptional, error.ErrorCode);
Assert.Equal(Strings.EdmModel_Validator_Semantic_RecursiveComplexTypedPropertyMustBeOptional("nested" + currentIndex), error.ErrorMessage);
currentIndex++;
}
}

#endregion

[Fact]
public void NavigationPropertyWrongMultiplicityForDependent()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3276,6 +3276,7 @@ public enum Microsoft.OData.Edm.Validation.EdmErrorCode : int {
RecordExpressionHasExtraProperties = 318
RecordExpressionMissingRequiredProperty = 317
RecordExpressionNotValidForNonStructuredType = 316
RecursiveComplexTypedPropertyMustBeOptional = 411
ReferencedTypeMustHaveValidName = 322
ReferenceElementMustContainAtLeastOneIncludeOrIncludeAnnotationsElement = 372
ReferentialConstraintPrincipalEndMustBelongToAssociation = 243
Expand Down Expand Up @@ -3473,6 +3474,7 @@ public sealed class Microsoft.OData.Edm.Validation.ValidationRules {
public static readonly Microsoft.OData.Edm.Validation.ValidationRule`1[[Microsoft.OData.Edm.IEdmProperty]] PropertyTypeCannotBeCollectionOfAbstractType = Microsoft.OData.Edm.Validation.ValidationRule`1[Microsoft.OData.Edm.IEdmProperty]
public static readonly Microsoft.OData.Edm.Validation.ValidationRule`1[[Microsoft.OData.Edm.Vocabularies.IEdmPropertyValueBinding]] PropertyValueBindingValueIsCorrectType = Microsoft.OData.Edm.Validation.ValidationRule`1[Microsoft.OData.Edm.Vocabularies.IEdmPropertyValueBinding]
public static readonly Microsoft.OData.Edm.Validation.ValidationRule`1[[Microsoft.OData.Edm.Vocabularies.IEdmRecordExpression]] RecordExpressionPropertiesMatchType = Microsoft.OData.Edm.Validation.ValidationRule`1[Microsoft.OData.Edm.Vocabularies.IEdmRecordExpression]
public static readonly Microsoft.OData.Edm.Validation.ValidationRule`1[[Microsoft.OData.Edm.IEdmStructuralProperty]] RecursiveComplexTypedPropertyMustBeOptional = Microsoft.OData.Edm.Validation.ValidationRule`1[Microsoft.OData.Edm.IEdmStructuralProperty]
public static readonly Microsoft.OData.Edm.Validation.ValidationRule`1[[Microsoft.OData.Edm.IEdmSchemaElement]] SchemaElementMustNotHaveKindOfNone = Microsoft.OData.Edm.Validation.ValidationRule`1[Microsoft.OData.Edm.IEdmSchemaElement]
public static readonly Microsoft.OData.Edm.Validation.ValidationRule`1[[Microsoft.OData.Edm.IEdmSchemaElement]] SchemaElementNamespaceIsNotAllowed = Microsoft.OData.Edm.Validation.ValidationRule`1[Microsoft.OData.Edm.IEdmSchemaElement]
public static readonly Microsoft.OData.Edm.Validation.ValidationRule`1[[Microsoft.OData.Edm.IEdmSchemaElement]] SchemaElementNamespaceIsTooLong = Microsoft.OData.Edm.Validation.ValidationRule`1[Microsoft.OData.Edm.IEdmSchemaElement]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3276,6 +3276,7 @@ public enum Microsoft.OData.Edm.Validation.EdmErrorCode : int {
RecordExpressionHasExtraProperties = 318
RecordExpressionMissingRequiredProperty = 317
RecordExpressionNotValidForNonStructuredType = 316
RecursiveComplexTypedPropertyMustBeOptional = 411
ReferencedTypeMustHaveValidName = 322
ReferenceElementMustContainAtLeastOneIncludeOrIncludeAnnotationsElement = 372
ReferentialConstraintPrincipalEndMustBelongToAssociation = 243
Expand Down Expand Up @@ -3473,6 +3474,7 @@ public sealed class Microsoft.OData.Edm.Validation.ValidationRules {
public static readonly Microsoft.OData.Edm.Validation.ValidationRule`1[[Microsoft.OData.Edm.IEdmProperty]] PropertyTypeCannotBeCollectionOfAbstractType = Microsoft.OData.Edm.Validation.ValidationRule`1[Microsoft.OData.Edm.IEdmProperty]
public static readonly Microsoft.OData.Edm.Validation.ValidationRule`1[[Microsoft.OData.Edm.Vocabularies.IEdmPropertyValueBinding]] PropertyValueBindingValueIsCorrectType = Microsoft.OData.Edm.Validation.ValidationRule`1[Microsoft.OData.Edm.Vocabularies.IEdmPropertyValueBinding]
public static readonly Microsoft.OData.Edm.Validation.ValidationRule`1[[Microsoft.OData.Edm.Vocabularies.IEdmRecordExpression]] RecordExpressionPropertiesMatchType = Microsoft.OData.Edm.Validation.ValidationRule`1[Microsoft.OData.Edm.Vocabularies.IEdmRecordExpression]
public static readonly Microsoft.OData.Edm.Validation.ValidationRule`1[[Microsoft.OData.Edm.IEdmStructuralProperty]] RecursiveComplexTypedPropertyMustBeOptional = Microsoft.OData.Edm.Validation.ValidationRule`1[Microsoft.OData.Edm.IEdmStructuralProperty]
public static readonly Microsoft.OData.Edm.Validation.ValidationRule`1[[Microsoft.OData.Edm.IEdmSchemaElement]] SchemaElementMustNotHaveKindOfNone = Microsoft.OData.Edm.Validation.ValidationRule`1[Microsoft.OData.Edm.IEdmSchemaElement]
public static readonly Microsoft.OData.Edm.Validation.ValidationRule`1[[Microsoft.OData.Edm.IEdmSchemaElement]] SchemaElementNamespaceIsNotAllowed = Microsoft.OData.Edm.Validation.ValidationRule`1[Microsoft.OData.Edm.IEdmSchemaElement]
public static readonly Microsoft.OData.Edm.Validation.ValidationRule`1[[Microsoft.OData.Edm.IEdmSchemaElement]] SchemaElementNamespaceIsTooLong = Microsoft.OData.Edm.Validation.ValidationRule`1[Microsoft.OData.Edm.IEdmSchemaElement]
Expand Down
Loading