45822d9d4fe3533fa7b7d1f851854e6d5903bcf7
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Metadata / EdmValidator.cs
1 //---------------------------------------------------------------------
2 // <copyright file="EdmValidator.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       [....]
7 // @backupOwner [....]
8 //---------------------------------------------------------------------
9
10 namespace System.Data.Metadata.Edm
11 {
12     using System;
13     using System.Collections;
14     using System.Collections.Generic;
15     using System.Diagnostics;
16
17     /// <summary>
18     /// The validation severity level
19     /// </summary>
20     internal enum ValidationSeverity
21     {
22         /// <summary>
23         /// Warning
24         /// </summary>
25         Warning,
26
27         /// <summary>
28         /// Error
29         /// </summary>
30         Error,
31
32         /// <summary>
33         /// Internal
34         /// </summary>
35         Internal
36     }
37
38     /// <summary>
39     /// Class representing a validtion error event args
40     /// </summary>
41     internal class ValidationErrorEventArgs : EventArgs
42     {
43         private EdmItemError _validationError;
44
45         /// <summary>
46         /// Construct the validation error event args with a validation error object
47         /// </summary>
48         /// <param name="validationError">The validation error object for this event args</param>
49         public ValidationErrorEventArgs(EdmItemError validationError)
50         {
51             _validationError = validationError;
52         }
53
54         /// <summary>
55         /// Gets the validation error object this event args
56         /// </summary>
57         public EdmItemError ValidationError
58         {
59             get
60             {
61                 return _validationError;
62             }
63         }
64     }
65
66     /// <summary>
67     /// Class for representing the validator
68     /// </summary>
69     internal class EdmValidator
70     {
71         private bool _skipReadOnlyItems;
72
73         /// <summary>
74         /// Gets or Sets whether the validator should skip readonly items
75         /// </summary>
76         internal bool SkipReadOnlyItems        
77         {
78             get
79             {
80                 return _skipReadOnlyItems;
81             }
82             set
83             {
84                 _skipReadOnlyItems = value;
85             }
86         }        
87
88         /// <summary>
89         /// Validate a collection of items in a batch
90         /// </summary>
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
96         {
97             EntityUtil.CheckArgumentNull(items, "items");
98             EntityUtil.CheckArgumentNull(items, "ospaceErrors");
99
100             HashSet<MetadataItem> validatedItems = new HashSet<MetadataItem>();
101
102             foreach (MetadataItem item in items)
103             {
104                 // Just call the internal helper method for each item
105                 InternalValidate(item, ospaceErrors, validatedItems);
106             }
107         }
108
109         /// <summary>
110         /// Event hook to perform preprocessing on the validation error before it gets added to a list of errors
111         /// </summary>
112         /// <param name="e">The event args for this event</param>
113         protected virtual void OnValidationError(ValidationErrorEventArgs e)
114         {
115         }
116
117         /// <summary>
118         /// Invoke the event hook Add an error to the list
119         /// </summary>
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)
123         {
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);
130         }
131
132         /// <summary>
133         /// Allows derived classes to perform additional validation
134         /// </summary>
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)
138         {
139             return null;
140         }
141
142         /// <summary>
143         /// Validate an item object
144         /// </summary>
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)
149         {
150             Debug.Assert(item != null, "InternalValidate is called with a null item, the caller should check for null first");
151
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) )
154             {
155                 return;
156             }
157
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);
161
162             // Check to make sure the item has an identity
163             if (string.IsNullOrEmpty(item.Identity))
164             {
165                 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_EmptyIdentity,item));
166             }
167
168             switch (item.BuiltInTypeKind)
169             {
170                 case BuiltInTypeKind.CollectionType:
171                     ValidateCollectionType((CollectionType)item, errors, validatedItems);
172                     break;
173                 case BuiltInTypeKind.ComplexType:
174                     ValidateComplexType((ComplexType)item, errors, validatedItems);
175                     break;
176                 case BuiltInTypeKind.EntityType:
177                     ValidateEntityType((EntityType)item, errors, validatedItems);
178                     break;
179                 case BuiltInTypeKind.Facet:
180                     ValidateFacet((Facet)item, errors, validatedItems);
181                     break;
182                 case BuiltInTypeKind.MetadataProperty:
183                     ValidateMetadataProperty((MetadataProperty)item, errors, validatedItems);
184                     break;
185                 case BuiltInTypeKind.NavigationProperty:
186                     ValidateNavigationProperty((NavigationProperty)item, errors, validatedItems);
187                     break;
188                 case BuiltInTypeKind.PrimitiveType:
189                     ValidatePrimitiveType((PrimitiveType)item, errors, validatedItems);
190                     break;
191                 case BuiltInTypeKind.EdmProperty:
192                     ValidateEdmProperty((EdmProperty)item, errors, validatedItems);
193                     break;
194                 case BuiltInTypeKind.RefType:
195                     ValidateRefType((RefType)item, errors, validatedItems);
196                     break;
197                 case BuiltInTypeKind.TypeUsage:
198                     ValidateTypeUsage((TypeUsage)item, errors, validatedItems);
199                     break;
200
201                 // Abstract classes
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");
211                     break;
212
213                 default:
214                     //Debug.Assert(false, String.Format(CultureInfo.InvariantCulture, "Validate not implemented for {0}", item.BuiltInTypeKind));
215                     break;
216             }
217
218             // Performs other custom validation
219             IEnumerable<EdmItemError> customErrors = CustomValidate(item);
220             if (customErrors != null)
221             {
222                 errors.AddRange(customErrors);
223             }
224         }
225
226         /// <summary>
227         /// Validate an CollectionType object
228         /// </summary>
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)
233         {
234             ValidateEdmType(item, errors, validatedItems);
235
236             // Check that it doesn't have a base type
237             if (item.BaseType != null)
238             {
239                 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_CollectionTypesCannotHaveBaseType, item));
240             }
241
242             if (item.TypeUsage == null)
243             {
244                 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_CollectionHasNoTypeUsage, item));
245             }
246             else
247             {
248                 // Just validate the element type, there is nothing on the collection itself to validate
249                 InternalValidate(item.TypeUsage, errors, validatedItems);
250             }
251         }
252
253         /// <summary>
254         /// Validate an ComplexType object
255         /// </summary>
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)
260         {
261             ValidateStructuralType(item, errors, validatedItems);
262         }
263
264         /// <summary>
265         /// Validate an EdmType object
266         /// </summary>
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)
271         {
272             ValidateItem(item, errors, validatedItems);
273
274             // Check that this type has a name and namespace
275             if (string.IsNullOrEmpty(item.Name))
276             {
277                 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_TypeHasNoName, item));
278             }
279             if (null == item.NamespaceName ||
280                 item.DataSpace != DataSpace.OSpace && string.Empty == item.NamespaceName)
281             {
282                 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_TypeHasNoNamespace, item));
283             }
284
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)
288             {
289                 // Validate the base type
290                 InternalValidate(item.BaseType, errors, validatedItems);
291             }
292         }
293
294         /// <summary>
295         /// Validate an EntityType object
296         /// </summary>
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)
301         {
302             // check the base EntityType has Keys
303             if (item.BaseType == null)
304             {
305                 // Check that there is at least one key member
306                 if (item.KeyMembers.Count < 1)
307                 {
308                     AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_NoKeyMembers(item.FullName), item));
309                 }
310                 else
311                 {
312                     foreach (EdmProperty keyProperty in item.KeyMembers)
313                     {
314                         if (keyProperty.Nullable)
315                         {
316                             AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_NullableEntityKeyProperty(keyProperty.Name, item.FullName), keyProperty));
317                         }
318                     }
319                 }
320             }
321
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);
325         }
326
327         /// <summary>
328         /// Validate an Facet object
329         /// </summary>
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)
334         {
335             ValidateItem(item, errors, validatedItems);
336
337             // Check that this facet has a name
338             if (string.IsNullOrEmpty(item.Name))
339             {
340                 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_FacetHasNoName, item));
341             }
342
343             // Validate the type
344             if (item.FacetType == null)
345             {
346                 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_FacetTypeIsNull, item));
347             }
348             else
349             {
350                 InternalValidate(item.FacetType, errors, validatedItems);
351             }
352         }
353
354         /// <summary>
355         /// Validate an MetadataItem object
356         /// </summary>
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)
361         {
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)
365             {
366                 foreach (MetadataProperty itemAttribute in item.MetadataProperties)
367                 {
368                     InternalValidate(itemAttribute, errors, validatedItems);
369                 }
370             }
371         }
372
373         /// <summary>
374         /// Validate an EdmMember object
375         /// </summary>
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)
380         {
381             ValidateItem(item, errors, validatedItems);
382
383             // Check that this member has a name
384             if (string.IsNullOrEmpty(item.Name))
385             {
386                 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_MemberHasNoName, item));
387             }
388
389             if (item.DeclaringType == null)
390             {
391                 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_MemberHasNullDeclaringType, item));
392             }
393             else
394             {
395                 InternalValidate(item.DeclaringType, errors, validatedItems);
396             }
397
398             if (item.TypeUsage == null)
399             {
400                 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_MemberHasNullTypeUsage, item));
401             }
402             else
403             {
404                 InternalValidate(item.TypeUsage, errors, validatedItems);
405             }
406         }
407
408         /// <summary>
409         /// Validate an MetadataProperty object
410         /// </summary>
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)
415         {
416             // Validate only for user added item attributes, for system attributes, we can skip validation
417             if (item.PropertyKind == PropertyKind.Extended)
418             {
419                 ValidateItem(item, errors, validatedItems);
420
421                 // Check that this member has a name
422                 if (string.IsNullOrEmpty(item.Name))
423                 {
424                     AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_MetadataPropertyHasNoName, item));
425                 }
426
427                 if (item.TypeUsage == null)
428                 {
429                     AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_ItemAttributeHasNullTypeUsage, item));
430                 }
431                 else
432                 {
433                     InternalValidate(item.TypeUsage, errors, validatedItems);
434                 }
435             }
436         }
437
438         /// <summary>
439         /// Validate an NavigationProperty object
440         /// </summary>
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)
445         {
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);
448         }
449
450         /// <summary>
451         /// Validate an GetPrimitiveType object
452         /// </summary>
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)
457         {
458             ValidateSimpleType(item, errors, validatedItems);
459         }
460
461         /// <summary>
462         /// Validate an EdmProperty object
463         /// </summary>
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)
468         {
469             ValidateEdmMember(item, errors, validatedItems);
470         }
471
472         /// <summary>
473         /// Validate an RefType object
474         /// </summary>
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)
479         {
480             ValidateEdmType(item, errors, validatedItems);
481
482             // Check that it doesn't have a base type
483             if (item.BaseType != null)
484             {
485                 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_RefTypesCannotHaveBaseType, item));
486             }
487
488             // Just validate the element type, there is nothing on the collection itself to validate
489             if (item.ElementType == null)
490             {
491                 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_RefTypeHasNullEntityType, null));
492             }
493             else
494             {
495                 InternalValidate(item.ElementType, errors, validatedItems);
496             }
497         }
498
499         /// <summary>
500         /// Validate an SimpleType object
501         /// </summary>
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)
506         {
507             ValidateEdmType(item, errors, validatedItems);
508         }
509
510         /// <summary>
511         /// Validate an StructuralType object
512         /// </summary>
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)
517         {
518             ValidateEdmType(item, errors, validatedItems);
519
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)
523             {
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))
527                 {                    
528                     AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_BaseTypeHasMemberOfSameName, item));
529                 }
530                 else
531                 {
532                     allMembers.Add(member.Name, member);
533                 }
534
535                 InternalValidate(member, errors, validatedItems);
536             }
537         }
538
539         /// <summary>
540         /// Validate an TypeUsage object
541         /// </summary>
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)
546         {
547             ValidateItem(item, errors, validatedItems);
548
549             if (item.EdmType == null)
550             {
551                 AddError(errors, new EdmItemError(System.Data.Entity.Strings.Validator_TypeUsageHasNullEdmType, item));
552             }
553             else
554             {
555                 InternalValidate(item.EdmType, errors, validatedItems);
556             }
557
558             foreach (Facet facet in item.Facets)
559             {
560                 InternalValidate(facet, errors, validatedItems);
561             }
562         }
563     }
564 }