e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
565 lines
24 KiB
C#
565 lines
24 KiB
C#
//---------------------------------------------------------------------
|
|
// <copyright file="EdmValidator.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//
|
|
// @owner [....]
|
|
// @backupOwner [....]
|
|
//---------------------------------------------------------------------
|
|
|
|
namespace System.Data.Metadata.Edm
|
|
{
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
|
|
/// <summary>
|
|
/// The validation severity level
|
|
/// </summary>
|
|
internal enum ValidationSeverity
|
|
{
|
|
/// <summary>
|
|
/// Warning
|
|
/// </summary>
|
|
Warning,
|
|
|
|
/// <summary>
|
|
/// Error
|
|
/// </summary>
|
|
Error,
|
|
|
|
/// <summary>
|
|
/// Internal
|
|
/// </summary>
|
|
Internal
|
|
}
|
|
|
|
/// <summary>
|
|
/// Class representing a validtion error event args
|
|
/// </summary>
|
|
internal class ValidationErrorEventArgs : EventArgs
|
|
{
|
|
private EdmItemError _validationError;
|
|
|
|
/// <summary>
|
|
/// Construct the validation error event args with a validation error object
|
|
/// </summary>
|
|
/// <param name="validationError">The validation error object for this event args</param>
|
|
public ValidationErrorEventArgs(EdmItemError validationError)
|
|
{
|
|
_validationError = validationError;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the validation error object this event args
|
|
/// </summary>
|
|
public EdmItemError ValidationError
|
|
{
|
|
get
|
|
{
|
|
return _validationError;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Class for representing the validator
|
|
/// </summary>
|
|
internal class EdmValidator
|
|
{
|
|
private bool _skipReadOnlyItems;
|
|
|
|
/// <summary>
|
|
/// Gets or Sets whether the validator should skip readonly items
|
|
/// </summary>
|
|
internal bool SkipReadOnlyItems
|
|
{
|
|
get
|
|
{
|
|
return _skipReadOnlyItems;
|
|
}
|
|
set
|
|
{
|
|
_skipReadOnlyItems = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate a collection of items in a batch
|
|
/// </summary>
|
|
/// <param name="items">A collection of items to validate</param>
|
|
/// <param name="ospaceErrors">List of validation errors that were previously collected by the caller. if it encounters
|
|
/// more errors, it adds them to this list of errors</param>
|
|
public void Validate<T>(IEnumerable<T> items, List<EdmItemError> ospaceErrors)
|
|
where T : EdmType // O-Space only supports EdmType
|
|
{
|
|
EntityUtil.CheckArgumentNull(items, "items");
|
|
EntityUtil.CheckArgumentNull(items, "ospaceErrors");
|
|
|
|
HashSet<MetadataItem> validatedItems = new HashSet<MetadataItem>();
|
|
|
|
foreach (MetadataItem item in items)
|
|
{
|
|
// Just call the internal helper method for each item
|
|
InternalValidate(item, ospaceErrors, validatedItems);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event hook to perform preprocessing on the validation error before it gets added to a list of errors
|
|
/// </summary>
|
|
/// <param name="e">The event args for this event</param>
|
|
protected virtual void OnValidationError(ValidationErrorEventArgs e)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invoke the event hook Add an error to the list
|
|
/// </summary>
|
|
/// <param name="errors">The list of errors to add to</param>
|
|
/// <param name="newError">The new error to add</param>
|
|
private void AddError(List<EdmItemError> errors, EdmItemError newError)
|
|
{
|
|
// Create an event args object and call the event hook, the derived class may have changed
|
|
// the validation error to some other object, in which case we add the validation error object
|
|
// coming from the event args
|
|
ValidationErrorEventArgs e = new ValidationErrorEventArgs(newError);
|
|
OnValidationError(e);
|
|
errors.Add(e.ValidationError);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows derived classes to perform additional validation
|
|
/// </summary>
|
|
/// <param name="item">The item to perform additional validation</param>
|
|
/// <returns>A collection of errors</returns>
|
|
protected virtual IEnumerable<EdmItemError> CustomValidate(MetadataItem item)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate an item object
|
|
/// </summary>
|
|
/// <param name="item">The item to validate</param>
|
|
/// <param name="errors">An error collection for adding validation errors</param>
|
|
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
|
|
private void InternalValidate(MetadataItem item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
|
|
{
|
|
Debug.Assert(item != null, "InternalValidate is called with a null item, the caller should check for null first");
|
|
|
|
// If the item has already been validated or we need to skip readonly items, then skip
|
|
if ( (item.IsReadOnly && SkipReadOnlyItems) || validatedItems.Contains(item) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Add this item to the dictionary so we won't validate this again. Note that we only do this
|
|
// in this function because every other function should eventually delegate to here
|
|
validatedItems.Add(item);
|
|
|
|
// Check to make sure the item has an identity
|
|
if (string.IsNullOrEmpty(item.Identity))
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_EmptyIdentity,item));
|
|
}
|
|
|
|
switch (item.BuiltInTypeKind)
|
|
{
|
|
case BuiltInTypeKind.CollectionType:
|
|
ValidateCollectionType((CollectionType)item, errors, validatedItems);
|
|
break;
|
|
case BuiltInTypeKind.ComplexType:
|
|
ValidateComplexType((ComplexType)item, errors, validatedItems);
|
|
break;
|
|
case BuiltInTypeKind.EntityType:
|
|
ValidateEntityType((EntityType)item, errors, validatedItems);
|
|
break;
|
|
case BuiltInTypeKind.Facet:
|
|
ValidateFacet((Facet)item, errors, validatedItems);
|
|
break;
|
|
case BuiltInTypeKind.MetadataProperty:
|
|
ValidateMetadataProperty((MetadataProperty)item, errors, validatedItems);
|
|
break;
|
|
case BuiltInTypeKind.NavigationProperty:
|
|
ValidateNavigationProperty((NavigationProperty)item, errors, validatedItems);
|
|
break;
|
|
case BuiltInTypeKind.PrimitiveType:
|
|
ValidatePrimitiveType((PrimitiveType)item, errors, validatedItems);
|
|
break;
|
|
case BuiltInTypeKind.EdmProperty:
|
|
ValidateEdmProperty((EdmProperty)item, errors, validatedItems);
|
|
break;
|
|
case BuiltInTypeKind.RefType:
|
|
ValidateRefType((RefType)item, errors, validatedItems);
|
|
break;
|
|
case BuiltInTypeKind.TypeUsage:
|
|
ValidateTypeUsage((TypeUsage)item, errors, validatedItems);
|
|
break;
|
|
|
|
// Abstract classes
|
|
case BuiltInTypeKind.EntityTypeBase:
|
|
case BuiltInTypeKind.EdmType:
|
|
case BuiltInTypeKind.MetadataItem:
|
|
case BuiltInTypeKind.EdmMember:
|
|
case BuiltInTypeKind.RelationshipEndMember:
|
|
case BuiltInTypeKind.RelationshipType:
|
|
case BuiltInTypeKind.SimpleType:
|
|
case BuiltInTypeKind.StructuralType:
|
|
Debug.Assert(false, "An instance with a built in type kind refering to the abstract type " + item.BuiltInTypeKind + " is encountered");
|
|
break;
|
|
|
|
default:
|
|
//Debug.Assert(false, String.Format(CultureInfo.InvariantCulture, "Validate not implemented for {0}", item.BuiltInTypeKind));
|
|
break;
|
|
}
|
|
|
|
// Performs other custom validation
|
|
IEnumerable<EdmItemError> customErrors = CustomValidate(item);
|
|
if (customErrors != null)
|
|
{
|
|
errors.AddRange(customErrors);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate an CollectionType object
|
|
/// </summary>
|
|
/// <param name="item">The CollectionType object to validate</param>
|
|
/// <param name="errors">An error collection for adding validation errors</param>
|
|
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
|
|
private void ValidateCollectionType(CollectionType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
|
|
{
|
|
ValidateEdmType(item, errors, validatedItems);
|
|
|
|
// Check that it doesn't have a base type
|
|
if (item.BaseType != null)
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_CollectionTypesCannotHaveBaseType, item));
|
|
}
|
|
|
|
if (item.TypeUsage == null)
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_CollectionHasNoTypeUsage, item));
|
|
}
|
|
else
|
|
{
|
|
// Just validate the element type, there is nothing on the collection itself to validate
|
|
InternalValidate(item.TypeUsage, errors, validatedItems);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate an ComplexType object
|
|
/// </summary>
|
|
/// <param name="item">The ComplexType object to validate</param>
|
|
/// <param name="errors">An error collection for adding validation errors</param>
|
|
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
|
|
private void ValidateComplexType(ComplexType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
|
|
{
|
|
ValidateStructuralType(item, errors, validatedItems);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate an EdmType object
|
|
/// </summary>
|
|
/// <param name="item">The EdmType object to validate</param>
|
|
/// <param name="errors">An error collection for adding validation errors</param>
|
|
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
|
|
private void ValidateEdmType(EdmType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
|
|
{
|
|
ValidateItem(item, errors, validatedItems);
|
|
|
|
// Check that this type has a name and namespace
|
|
if (string.IsNullOrEmpty(item.Name))
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_TypeHasNoName, item));
|
|
}
|
|
if (null == item.NamespaceName ||
|
|
item.DataSpace != DataSpace.OSpace && string.Empty == item.NamespaceName)
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_TypeHasNoNamespace, item));
|
|
}
|
|
|
|
// We don't need to verify that the base type chain eventually gets to null because
|
|
// the CLR doesn't allow loops in class hierarchies.
|
|
if (item.BaseType != null)
|
|
{
|
|
// Validate the base type
|
|
InternalValidate(item.BaseType, errors, validatedItems);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate an EntityType object
|
|
/// </summary>
|
|
/// <param name="item">The EntityType object to validate</param>
|
|
/// <param name="errors">An error collection for adding validation errors</param>
|
|
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
|
|
private void ValidateEntityType(EntityType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
|
|
{
|
|
// check the base EntityType has Keys
|
|
if (item.BaseType == null)
|
|
{
|
|
// Check that there is at least one key member
|
|
if (item.KeyMembers.Count < 1)
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_NoKeyMembers(item.FullName), item));
|
|
}
|
|
else
|
|
{
|
|
foreach (EdmProperty keyProperty in item.KeyMembers)
|
|
{
|
|
if (keyProperty.Nullable)
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_NullableEntityKeyProperty(keyProperty.Name, item.FullName), keyProperty));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Continue to process the entity to see if there are other errors. This allows the user to
|
|
// fix as much as possible at the same time.
|
|
ValidateStructuralType(item, errors, validatedItems);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate an Facet object
|
|
/// </summary>
|
|
/// <param name="item">The Facet object to validate</param>
|
|
/// <param name="errors">An error collection for adding validation errors</param>
|
|
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
|
|
private void ValidateFacet(Facet item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
|
|
{
|
|
ValidateItem(item, errors, validatedItems);
|
|
|
|
// Check that this facet has a name
|
|
if (string.IsNullOrEmpty(item.Name))
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_FacetHasNoName, item));
|
|
}
|
|
|
|
// Validate the type
|
|
if (item.FacetType == null)
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_FacetTypeIsNull, item));
|
|
}
|
|
else
|
|
{
|
|
InternalValidate(item.FacetType, errors, validatedItems);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate an MetadataItem object
|
|
/// </summary>
|
|
/// <param name="item">The MetadataItem object to validate</param>
|
|
/// <param name="errors">An error collection for adding validation errors</param>
|
|
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
|
|
private void ValidateItem(MetadataItem item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
|
|
{
|
|
// In here, we look at RawMetadataProperties because it dynamically add MetadataProperties when you access the
|
|
// normal MetadataProperties property. This avoids needless validation and infinite recursion
|
|
if (item.RawMetadataProperties != null)
|
|
{
|
|
foreach (MetadataProperty itemAttribute in item.MetadataProperties)
|
|
{
|
|
InternalValidate(itemAttribute, errors, validatedItems);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate an EdmMember object
|
|
/// </summary>
|
|
/// <param name="item">The item object to validate</param>
|
|
/// <param name="errors">An error collection for adding validation errors</param>
|
|
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
|
|
private void ValidateEdmMember(EdmMember item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
|
|
{
|
|
ValidateItem(item, errors, validatedItems);
|
|
|
|
// Check that this member has a name
|
|
if (string.IsNullOrEmpty(item.Name))
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_MemberHasNoName, item));
|
|
}
|
|
|
|
if (item.DeclaringType == null)
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_MemberHasNullDeclaringType, item));
|
|
}
|
|
else
|
|
{
|
|
InternalValidate(item.DeclaringType, errors, validatedItems);
|
|
}
|
|
|
|
if (item.TypeUsage == null)
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_MemberHasNullTypeUsage, item));
|
|
}
|
|
else
|
|
{
|
|
InternalValidate(item.TypeUsage, errors, validatedItems);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate an MetadataProperty object
|
|
/// </summary>
|
|
/// <param name="item">The MetadataProperty object to validate</param>
|
|
/// <param name="errors">An error collection for adding validation errors</param>
|
|
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
|
|
private void ValidateMetadataProperty(MetadataProperty item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
|
|
{
|
|
// Validate only for user added item attributes, for system attributes, we can skip validation
|
|
if (item.PropertyKind == PropertyKind.Extended)
|
|
{
|
|
ValidateItem(item, errors, validatedItems);
|
|
|
|
// Check that this member has a name
|
|
if (string.IsNullOrEmpty(item.Name))
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_MetadataPropertyHasNoName, item));
|
|
}
|
|
|
|
if (item.TypeUsage == null)
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_ItemAttributeHasNullTypeUsage, item));
|
|
}
|
|
else
|
|
{
|
|
InternalValidate(item.TypeUsage, errors, validatedItems);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate an NavigationProperty object
|
|
/// </summary>
|
|
/// <param name="item">The NavigationProperty object to validate</param>
|
|
/// <param name="errors">An error collection for adding validation errors</param>
|
|
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
|
|
private void ValidateNavigationProperty(NavigationProperty item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
|
|
{
|
|
// Continue to process the property to see if there are other errors. This allows the user to fix as much as possible at the same time.
|
|
ValidateEdmMember(item, errors, validatedItems);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate an GetPrimitiveType object
|
|
/// </summary>
|
|
/// <param name="item">The GetPrimitiveType object to validate</param>
|
|
/// <param name="errors">An error collection for adding validation errors</param>
|
|
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
|
|
private void ValidatePrimitiveType(PrimitiveType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
|
|
{
|
|
ValidateSimpleType(item, errors, validatedItems);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate an EdmProperty object
|
|
/// </summary>
|
|
/// <param name="item">The EdmProperty object to validate</param>
|
|
/// <param name="errors">An error collection for adding validation errors</param>
|
|
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
|
|
private void ValidateEdmProperty(EdmProperty item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
|
|
{
|
|
ValidateEdmMember(item, errors, validatedItems);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate an RefType object
|
|
/// </summary>
|
|
/// <param name="item">The RefType object to validate</param>
|
|
/// <param name="errors">An error collection for adding validation errors</param>
|
|
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
|
|
private void ValidateRefType(RefType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
|
|
{
|
|
ValidateEdmType(item, errors, validatedItems);
|
|
|
|
// Check that it doesn't have a base type
|
|
if (item.BaseType != null)
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_RefTypesCannotHaveBaseType, item));
|
|
}
|
|
|
|
// Just validate the element type, there is nothing on the collection itself to validate
|
|
if (item.ElementType == null)
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_RefTypeHasNullEntityType, null));
|
|
}
|
|
else
|
|
{
|
|
InternalValidate(item.ElementType, errors, validatedItems);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate an SimpleType object
|
|
/// </summary>
|
|
/// <param name="item">The SimpleType object to validate</param>
|
|
/// <param name="errors">An error collection for adding validation errors</param>
|
|
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
|
|
private void ValidateSimpleType(SimpleType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
|
|
{
|
|
ValidateEdmType(item, errors, validatedItems);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate an StructuralType object
|
|
/// </summary>
|
|
/// <param name="item">The StructuralType object to validate</param>
|
|
/// <param name="errors">An error collection for adding validation errors</param>
|
|
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
|
|
private void ValidateStructuralType(StructuralType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
|
|
{
|
|
ValidateEdmType(item, errors, validatedItems);
|
|
|
|
// Just validate each member, the collection already guaranteed that there aren't any nulls in the collection
|
|
Dictionary<string, EdmMember> allMembers = new Dictionary<string, EdmMember>();
|
|
foreach (EdmMember member in item.Members)
|
|
{
|
|
// Check if the base type already has a member of the same name
|
|
EdmMember baseMember = null;
|
|
if (allMembers.TryGetValue(member.Name, out baseMember))
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_BaseTypeHasMemberOfSameName, item));
|
|
}
|
|
else
|
|
{
|
|
allMembers.Add(member.Name, member);
|
|
}
|
|
|
|
InternalValidate(member, errors, validatedItems);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validate an TypeUsage object
|
|
/// </summary>
|
|
/// <param name="item">The TypeUsage object to validate</param>
|
|
/// <param name="errors">An error collection for adding validation errors</param>
|
|
/// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
|
|
private void ValidateTypeUsage(TypeUsage item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
|
|
{
|
|
ValidateItem(item, errors, validatedItems);
|
|
|
|
if (item.EdmType == null)
|
|
{
|
|
AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_TypeUsageHasNullEdmType, item));
|
|
}
|
|
else
|
|
{
|
|
InternalValidate(item.EdmType, errors, validatedItems);
|
|
}
|
|
|
|
foreach (Facet facet in item.Facets)
|
|
{
|
|
InternalValidate(facet, errors, validatedItems);
|
|
}
|
|
}
|
|
}
|
|
}
|