1 //---------------------------------------------------------------------
2 // <copyright file="EdmValidator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 //---------------------------------------------------------------------
10 namespace System.Data.Metadata.Edm
13 using System.Collections;
14 using System.Collections.Generic;
15 using System.Diagnostics;
18 /// The validation severity level
20 internal enum ValidationSeverity
39 /// Class representing a validtion error event args
41 internal class ValidationErrorEventArgs : EventArgs
43 private EdmItemError _validationError;
46 /// Construct the validation error event args with a validation error object
48 /// <param name="validationError">The validation error object for this event args</param>
49 public ValidationErrorEventArgs(EdmItemError validationError)
51 _validationError = validationError;
55 /// Gets the validation error object this event args
57 public EdmItemError ValidationError
61 return _validationError;
67 /// Class for representing the validator
69 internal class EdmValidator
71 private bool _skipReadOnlyItems;
74 /// Gets or Sets whether the validator should skip readonly items
76 internal bool SkipReadOnlyItems
80 return _skipReadOnlyItems;
84 _skipReadOnlyItems = value;
89 /// Validate a collection of items in a batch
91 /// <param name="items">A collection of items to validate</param>
92 /// <param name="ospaceErrors">List of validation errors that were previously collected by the caller. if it encounters
93 /// more errors, it adds them to this list of errors</param>
94 public void Validate<T>(IEnumerable<T> items, List<EdmItemError> ospaceErrors)
95 where T : EdmType // O-Space only supports EdmType
97 EntityUtil.CheckArgumentNull(items, "items");
98 EntityUtil.CheckArgumentNull(items, "ospaceErrors");
100 HashSet<MetadataItem> validatedItems = new HashSet<MetadataItem>();
102 foreach (MetadataItem item in items)
104 // Just call the internal helper method for each item
105 InternalValidate(item, ospaceErrors, validatedItems);
110 /// Event hook to perform preprocessing on the validation error before it gets added to a list of errors
112 /// <param name="e">The event args for this event</param>
113 protected virtual void OnValidationError(ValidationErrorEventArgs e)
118 /// Invoke the event hook Add an error to the list
120 /// <param name="errors">The list of errors to add to</param>
121 /// <param name="newError">The new error to add</param>
122 private void AddError(List<EdmItemError> errors, EdmItemError newError)
124 // Create an event args object and call the event hook, the derived class may have changed
125 // the validation error to some other object, in which case we add the validation error object
126 // coming from the event args
127 ValidationErrorEventArgs e = new ValidationErrorEventArgs(newError);
128 OnValidationError(e);
129 errors.Add(e.ValidationError);
133 /// Allows derived classes to perform additional validation
135 /// <param name="item">The item to perform additional validation</param>
136 /// <returns>A collection of errors</returns>
137 protected virtual IEnumerable<EdmItemError> CustomValidate(MetadataItem item)
143 /// Validate an item object
145 /// <param name="item">The item to validate</param>
146 /// <param name="errors">An error collection for adding validation errors</param>
147 /// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
148 private void InternalValidate(MetadataItem item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
150 Debug.Assert(item != null, "InternalValidate is called with a null item, the caller should check for null first");
152 // If the item has already been validated or we need to skip readonly items, then skip
153 if ( (item.IsReadOnly && SkipReadOnlyItems) || validatedItems.Contains(item) )
158 // Add this item to the dictionary so we won't validate this again. Note that we only do this
159 // in this function because every other function should eventually delegate to here
160 validatedItems.Add(item);
162 // Check to make sure the item has an identity
163 if (string.IsNullOrEmpty(item.Identity))
165 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_EmptyIdentity,item));
168 switch (item.BuiltInTypeKind)
170 case BuiltInTypeKind.CollectionType:
171 ValidateCollectionType((CollectionType)item, errors, validatedItems);
173 case BuiltInTypeKind.ComplexType:
174 ValidateComplexType((ComplexType)item, errors, validatedItems);
176 case BuiltInTypeKind.EntityType:
177 ValidateEntityType((EntityType)item, errors, validatedItems);
179 case BuiltInTypeKind.Facet:
180 ValidateFacet((Facet)item, errors, validatedItems);
182 case BuiltInTypeKind.MetadataProperty:
183 ValidateMetadataProperty((MetadataProperty)item, errors, validatedItems);
185 case BuiltInTypeKind.NavigationProperty:
186 ValidateNavigationProperty((NavigationProperty)item, errors, validatedItems);
188 case BuiltInTypeKind.PrimitiveType:
189 ValidatePrimitiveType((PrimitiveType)item, errors, validatedItems);
191 case BuiltInTypeKind.EdmProperty:
192 ValidateEdmProperty((EdmProperty)item, errors, validatedItems);
194 case BuiltInTypeKind.RefType:
195 ValidateRefType((RefType)item, errors, validatedItems);
197 case BuiltInTypeKind.TypeUsage:
198 ValidateTypeUsage((TypeUsage)item, errors, validatedItems);
202 case BuiltInTypeKind.EntityTypeBase:
203 case BuiltInTypeKind.EdmType:
204 case BuiltInTypeKind.MetadataItem:
205 case BuiltInTypeKind.EdmMember:
206 case BuiltInTypeKind.RelationshipEndMember:
207 case BuiltInTypeKind.RelationshipType:
208 case BuiltInTypeKind.SimpleType:
209 case BuiltInTypeKind.StructuralType:
210 Debug.Assert(false, "An instance with a built in type kind refering to the abstract type " + item.BuiltInTypeKind + " is encountered");
214 //Debug.Assert(false, String.Format(CultureInfo.InvariantCulture, "Validate not implemented for {0}", item.BuiltInTypeKind));
218 // Performs other custom validation
219 IEnumerable<EdmItemError> customErrors = CustomValidate(item);
220 if (customErrors != null)
222 errors.AddRange(customErrors);
227 /// Validate an CollectionType object
229 /// <param name="item">The CollectionType object to validate</param>
230 /// <param name="errors">An error collection for adding validation errors</param>
231 /// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
232 private void ValidateCollectionType(CollectionType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
234 ValidateEdmType(item, errors, validatedItems);
236 // Check that it doesn't have a base type
237 if (item.BaseType != null)
239 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_CollectionTypesCannotHaveBaseType, item));
242 if (item.TypeUsage == null)
244 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_CollectionHasNoTypeUsage, item));
248 // Just validate the element type, there is nothing on the collection itself to validate
249 InternalValidate(item.TypeUsage, errors, validatedItems);
254 /// Validate an ComplexType object
256 /// <param name="item">The ComplexType object to validate</param>
257 /// <param name="errors">An error collection for adding validation errors</param>
258 /// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
259 private void ValidateComplexType(ComplexType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
261 ValidateStructuralType(item, errors, validatedItems);
265 /// Validate an EdmType object
267 /// <param name="item">The EdmType object to validate</param>
268 /// <param name="errors">An error collection for adding validation errors</param>
269 /// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
270 private void ValidateEdmType(EdmType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
272 ValidateItem(item, errors, validatedItems);
274 // Check that this type has a name and namespace
275 if (string.IsNullOrEmpty(item.Name))
277 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_TypeHasNoName, item));
279 if (null == item.NamespaceName ||
280 item.DataSpace != DataSpace.OSpace && string.Empty == item.NamespaceName)
282 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_TypeHasNoNamespace, item));
285 // We don't need to verify that the base type chain eventually gets to null because
286 // the CLR doesn't allow loops in class hierarchies.
287 if (item.BaseType != null)
289 // Validate the base type
290 InternalValidate(item.BaseType, errors, validatedItems);
295 /// Validate an EntityType object
297 /// <param name="item">The EntityType object to validate</param>
298 /// <param name="errors">An error collection for adding validation errors</param>
299 /// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
300 private void ValidateEntityType(EntityType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
302 // check the base EntityType has Keys
303 if (item.BaseType == null)
305 // Check that there is at least one key member
306 if (item.KeyMembers.Count < 1)
308 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_NoKeyMembers(item.FullName), item));
312 foreach (EdmProperty keyProperty in item.KeyMembers)
314 if (keyProperty.Nullable)
316 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_NullableEntityKeyProperty(keyProperty.Name, item.FullName), keyProperty));
322 // Continue to process the entity to see if there are other errors. This allows the user to
323 // fix as much as possible at the same time.
324 ValidateStructuralType(item, errors, validatedItems);
328 /// Validate an Facet object
330 /// <param name="item">The Facet object to validate</param>
331 /// <param name="errors">An error collection for adding validation errors</param>
332 /// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
333 private void ValidateFacet(Facet item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
335 ValidateItem(item, errors, validatedItems);
337 // Check that this facet has a name
338 if (string.IsNullOrEmpty(item.Name))
340 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_FacetHasNoName, item));
344 if (item.FacetType == null)
346 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_FacetTypeIsNull, item));
350 InternalValidate(item.FacetType, errors, validatedItems);
355 /// Validate an MetadataItem object
357 /// <param name="item">The MetadataItem object to validate</param>
358 /// <param name="errors">An error collection for adding validation errors</param>
359 /// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
360 private void ValidateItem(MetadataItem item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
362 // In here, we look at RawMetadataProperties because it dynamically add MetadataProperties when you access the
363 // normal MetadataProperties property. This avoids needless validation and infinite recursion
364 if (item.RawMetadataProperties != null)
366 foreach (MetadataProperty itemAttribute in item.MetadataProperties)
368 InternalValidate(itemAttribute, errors, validatedItems);
374 /// Validate an EdmMember object
376 /// <param name="item">The item object to validate</param>
377 /// <param name="errors">An error collection for adding validation errors</param>
378 /// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
379 private void ValidateEdmMember(EdmMember item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
381 ValidateItem(item, errors, validatedItems);
383 // Check that this member has a name
384 if (string.IsNullOrEmpty(item.Name))
386 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_MemberHasNoName, item));
389 if (item.DeclaringType == null)
391 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_MemberHasNullDeclaringType, item));
395 InternalValidate(item.DeclaringType, errors, validatedItems);
398 if (item.TypeUsage == null)
400 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_MemberHasNullTypeUsage, item));
404 InternalValidate(item.TypeUsage, errors, validatedItems);
409 /// Validate an MetadataProperty object
411 /// <param name="item">The MetadataProperty object to validate</param>
412 /// <param name="errors">An error collection for adding validation errors</param>
413 /// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
414 private void ValidateMetadataProperty(MetadataProperty item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
416 // Validate only for user added item attributes, for system attributes, we can skip validation
417 if (item.PropertyKind == PropertyKind.Extended)
419 ValidateItem(item, errors, validatedItems);
421 // Check that this member has a name
422 if (string.IsNullOrEmpty(item.Name))
424 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_MetadataPropertyHasNoName, item));
427 if (item.TypeUsage == null)
429 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_ItemAttributeHasNullTypeUsage, item));
433 InternalValidate(item.TypeUsage, errors, validatedItems);
439 /// Validate an NavigationProperty object
441 /// <param name="item">The NavigationProperty object to validate</param>
442 /// <param name="errors">An error collection for adding validation errors</param>
443 /// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
444 private void ValidateNavigationProperty(NavigationProperty item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
446 // 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.
447 ValidateEdmMember(item, errors, validatedItems);
451 /// Validate an GetPrimitiveType object
453 /// <param name="item">The GetPrimitiveType object to validate</param>
454 /// <param name="errors">An error collection for adding validation errors</param>
455 /// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
456 private void ValidatePrimitiveType(PrimitiveType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
458 ValidateSimpleType(item, errors, validatedItems);
462 /// Validate an EdmProperty object
464 /// <param name="item">The EdmProperty object to validate</param>
465 /// <param name="errors">An error collection for adding validation errors</param>
466 /// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
467 private void ValidateEdmProperty(EdmProperty item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
469 ValidateEdmMember(item, errors, validatedItems);
473 /// Validate an RefType object
475 /// <param name="item">The RefType object to validate</param>
476 /// <param name="errors">An error collection for adding validation errors</param>
477 /// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
478 private void ValidateRefType(RefType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
480 ValidateEdmType(item, errors, validatedItems);
482 // Check that it doesn't have a base type
483 if (item.BaseType != null)
485 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_RefTypesCannotHaveBaseType, item));
488 // Just validate the element type, there is nothing on the collection itself to validate
489 if (item.ElementType == null)
491 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_RefTypeHasNullEntityType, null));
495 InternalValidate(item.ElementType, errors, validatedItems);
500 /// Validate an SimpleType object
502 /// <param name="item">The SimpleType object to validate</param>
503 /// <param name="errors">An error collection for adding validation errors</param>
504 /// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
505 private void ValidateSimpleType(SimpleType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
507 ValidateEdmType(item, errors, validatedItems);
511 /// Validate an StructuralType object
513 /// <param name="item">The StructuralType object to validate</param>
514 /// <param name="errors">An error collection for adding validation errors</param>
515 /// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
516 private void ValidateStructuralType(StructuralType item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
518 ValidateEdmType(item, errors, validatedItems);
520 // Just validate each member, the collection already guaranteed that there aren't any nulls in the collection
521 Dictionary<string, EdmMember> allMembers = new Dictionary<string, EdmMember>();
522 foreach (EdmMember member in item.Members)
524 // Check if the base type already has a member of the same name
525 EdmMember baseMember = null;
526 if (allMembers.TryGetValue(member.Name, out baseMember))
528 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_BaseTypeHasMemberOfSameName, item));
532 allMembers.Add(member.Name, member);
535 InternalValidate(member, errors, validatedItems);
540 /// Validate an TypeUsage object
542 /// <param name="item">The TypeUsage object to validate</param>
543 /// <param name="errors">An error collection for adding validation errors</param>
544 /// <param name="validatedItems">A dictionary keeping track of items that have been validated</param>
545 private void ValidateTypeUsage(TypeUsage item, List<EdmItemError> errors, HashSet<MetadataItem> validatedItems)
547 ValidateItem(item, errors, validatedItems);
549 if (item.EdmType == null)
551 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_TypeUsageHasNullEdmType, item));
555 InternalValidate(item.EdmType, errors, validatedItems);
558 foreach (Facet facet in item.Facets)
560 InternalValidate(facet, errors, validatedItems);