/// <summary> /// Recursively validates <paramref name="entity"/> by traversing all of its properties /// </summary> /// <param name="entity">The object to validate</param> /// <param name="parentContext">The rule context of the object that <paramref name="entity"/> belongs to</param> /// <param name="rules">The set of rules from the parent object to apply to <paramref name="entity"/></param> /// <param name="traverseProperties">Whether or not to traverse this <paramref name="entity"/>'s properties</param> /// <returns></returns> private IEnumerable <LogMessage> RecursiveValidate(object entity, ObjectPath entityPath, RuleContext parentContext, IEnumerable <Rule> rules, bool traverseProperties = true) { var messages = Enumerable.Empty <LogMessage>(); if (entity == null) { return(messages); } // Ensure that the rules can be re-enumerated without re-evaluating the enumeration. var collectionRules = rules.ReEnumerable(); var list = entity as IList; var dictionary = entity as IDictionary; if (traverseProperties && list != null) { // Recursively validate each list item and add the // item index to the location of each validation message var listMessages = list.SelectMany((item, index) => RecursiveValidate(item, entityPath.AppendIndex(index), parentContext.CreateChild(item, index), collectionRules)); messages = messages.Concat(listMessages); } else if (traverseProperties && dictionary != null) { // Dictionaries that don't provide any type info cannot be traversed, since it result in infinite iteration var shouldTraverseEntries = dictionary.IsTraversableDictionary(); // Recursively validate each dictionary entry and add the entry // key to the location of each validation message var dictMessages = dictionary.SelectMany((key, value) => RecursiveValidate(value, entityPath.AppendProperty((string)key), parentContext.CreateChild(value, (string)key), collectionRules, shouldTraverseEntries)); messages = messages.Concat(dictMessages); } // If this is a class, validate its value and its properties. else if (traverseProperties && entity.GetType().IsClass() && entity.GetType() != typeof(string)) { // Validate each property of the object var propertyMessages = entity.GetValidatableProperties() .SelectMany(p => ValidateProperty(p, p.GetValue(entity), entityPath.AppendProperty(p.Name), parentContext)); messages = messages.Concat(propertyMessages); } // Validate the value of the object itself var valueMessages = ValidateObjectValue(entity, collectionRules, parentContext); return(messages.Concat(valueMessages)); }
public void PushProperty(string property) { _path.Push(Path.AppendProperty(property)); }
/// <summary> /// Merges to Yaml mapping nodes (~ dictionaries) as follows: /// - members existing only in one node will be present in the result /// - members existing in both nodes will /// - be present in the result, if they are identical /// - be merged if they are mapping or sequence nodes /// - THROW an exception otherwise /// </summary> public static T MergeYamlObjects <T>(T a, T b, ObjectPath path) where T : YamlNode { if (a == null) { throw new ArgumentNullException(nameof(a)); } if (b == null) { throw new ArgumentNullException(nameof(b)); } // trivial case if (a.Equals(b)) { return(a); } // mapping nodes var aMapping = a as YamlMappingNode; var bMapping = b as YamlMappingNode; if (aMapping != null && bMapping != null) { // iterate all members var result = new YamlMappingNode(); var keys = aMapping.Children.Keys.Concat(bMapping.Children.Keys).Distinct(); foreach (var key in keys) { var subpath = path.AppendProperty(key.ToString()); // forward if only present in one of the nodes if (!aMapping.Children.ContainsKey(key)) { result.Children.Add(key, bMapping.Children[key]); continue; } if (!bMapping.Children.ContainsKey(key)) { result.Children.Add(key, aMapping.Children[key]); continue; } // try merge objects otherwise var aMember = aMapping.Children[key]; var bMember = bMapping.Children[key]; result.Children.Add(key, MergeYamlObjects(aMember, bMember, subpath)); } return(result as T); } // sequence nodes var aSequence = a as YamlSequenceNode; var bSequence = b as YamlSequenceNode; if (aSequence != null && bSequence != null) { return(new YamlSequenceNode(aSequence.Children.Concat(bSequence.Children).Distinct()) as T); } // nothing worked throw new Exception($"{path.XPath} has incomaptible values ({a}, {b})."); }