/// <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));
        }
Exemple #2
0
 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}).");
        }