private static void UpdateFailedResultWithArrayElementsDetails(
            FailedResult outputResultToUpdate,
            PropertyInfo collectionProperty,
            Type instanceType,
            object collectionValue)
        {
            if (collectionValue == null)
            {
                return;
            }

            var attributesFromValidateCollection = CustomAttributeExtensions.GetCustomAttributes(collectionProperty, true)
                                                   .OfType <ValidateCollectionAttribute>()
                                                   .Select(a => a.InlineAttribute)
                                                   .ToList();

            var counter = -1;

            foreach (var arrayElement in (System.Collections.IEnumerable)collectionValue)
            {
                counter++;

                if (arrayElement == null)
                {
                    if (attributesFromValidateCollection.Any(a => a.GetType() == typeof(RequiredAttribute)))
                    {
                        var source = $"{instanceType.Name}.{collectionProperty.Name}.{counter}";
                        outputResultToUpdate.AddError(new ExecutionError(AnnotationErrorCodes.Required, source));
                    }

                    continue;
                }

                // Validate every array element itself.
                var arrayResultErrors = CoreValidator.GetValidationErrors(arrayElement, new ValidationContext(arrayElement), attributesFromValidateCollection, false);
                foreach (var arrayElementError in arrayResultErrors)
                {
                    var source = $"{instanceType.Name}.{collectionProperty.Name}.{counter}";
                    outputResultToUpdate.AddError(new ExecutionError(arrayElementError.CodeInfo, source));
                }

                var arrayType = arrayElement.GetType();
                if (arrayType.IsPrimitive || arrayType == typeof(decimal) || arrayType == typeof(string))
                {
                    continue;
                }

                // Validate properties of an array element recursively if it's not an primitive type.
                var propertyResult = Validate(arrayElement);
                if (!(propertyResult is FailedResult fr))
                {
                    continue;
                }

                UpdateFailedResultWithDetails(outputResultToUpdate, fr, collectionProperty, instanceType, counter);
            }
        }
        /// <summary>
        /// Extension that filters needed internal errors and fills sources if needed.
        /// </summary>
        /// <typeparam name="TCommand">Type of command.</typeparam>
        /// <param name="executionResult">Failed Execution Result.</param>
        /// <returns>Filed FailedResult with correct Source.</returns>
        public static IExecutionResult ForCommand <TCommand>(this IExecutionResult executionResult)
            where TCommand : ICommand
        {
            if (!(executionResult is FailedResult failedResult))
            {
                return(executionResult);
            }

            var commandName = typeof(TCommand).Name;
            var result      = new FailedResult(failedResult.CodeInfo, commandName, failedResult.Message);

            // Don't take internal errors if source used for another command.
            foreach (var error in failedResult.Details)
            {
                if (!(string.IsNullOrEmpty(error.Source) || error.Source.Contains(commandName)))
                {
                    // We don't need to take internal error used for another command.
                    continue;
                }

                var internalSource = string.IsNullOrEmpty(error.Source)
                    ? typeof(TCommand).Name
                    : error.Source;

                var newInternalError = new ExecutionError(error.CodeInfo, internalSource, error.Message);

                result.AddError(newInternalError);
            }

            return(result);
        }
示例#3
0
        public void FailedResultSerializeTestWithDetails()
        {
            var input           = new FailedResult(CoreErrorCodes.ValidationFailed, "Some source");
            var executionError1 = new ExecutionError(CoreErrorCodes.NameIsInUse, "Source 1", "Message 1");
            var executionError2 = new ExecutionError(CoreErrorCodes.NameIsNotFound, "Source 2", "Message 2");

            input.AddError(executionError1);
            input.AddError(executionError2);

            var json         = input.ToJson();
            var deserialized = json.FromJson <FailedResult>();

            deserialized.Message.Should().Be(input.Message);
            deserialized.Code.Should().Be(input.Code);
            deserialized.Source.Should().Be(input.Source);
            deserialized.CodeInfo.Should().BeEquivalentTo(input.CodeInfo);

            deserialized.Details.Count().Should().Be(2);
            deserialized.Details.First().Should().BeEquivalentTo(executionError1);
            deserialized.Details.Last().Should().BeEquivalentTo(executionError2);
        }
        /// <summary>
        /// Validates instance using DataAnnotations.
        /// </summary>
        /// <param name="instance">Validated instance.</param>
        /// <returns><see cref="FailedResult"/> if invalid, otherwise <see cref="SuccessfulResult"/>.</returns>
        public static IExecutionResult Validate(object instance)
        {
            if (instance == null)
            {
                return(new SuccessfulResult());
            }

            var validationContext = new ValidationContext(instance);
            var executionErrors   = new List <ExecutionError>();

            // Validate current level of the instance.
            CoreValidator.TryValidateObject(instance, validationContext, executionErrors, true);

            var instanceType = instance.GetType();
            var result       = new FailedResult(CoreErrorCodes.ValidationFailed, instanceType.Name);

            foreach (var executionError in executionErrors)
            {
                var field  = executionError.Source ?? string.Empty;
                var source = $"{instanceType.Name}.{field}";
                var error  = new ExecutionError(executionError.CodeInfo, source);

                result.AddError(error);
            }

            // Recursively validate nested types.
            var properties = instanceType
                             .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty)
                             .Where(PropertyCanBeValidated);

            foreach (var property in properties)
            {
                var propertyValue = property.GetValue(instance);

                if (typeof(System.Collections.IEnumerable).IsAssignableFrom(property.PropertyType))
                {
                    UpdateFailedResultWithArrayElementsDetails(result, property, instanceType, propertyValue);
                    continue;
                }

                var propertyResult = Validate(propertyValue);
                if (!(propertyResult is FailedResult fr))
                {
                    continue;
                }

                UpdateFailedResultWithDetails(result, fr, property, instanceType);
            }

            return(result.Details.Any()
                ? (IExecutionResult)result
                : new SuccessfulResult());
        }
        private static void UpdateFailedResultWithDetails(
            FailedResult outputResultToUpdate,
            FailedResult takeDetailsFrom,
            PropertyInfo property,
            Type instanceType,
            int?indexCounter = null)
        {
            var nestedExecutionErrors = takeDetailsFrom.Details.Select(error =>
            {
                var updatedInternalSource = indexCounter == null
                    ? error.Source.Replace(takeDetailsFrom.Source, property.Name)
                    : error.Source.Replace(takeDetailsFrom.Source, $"{property.Name}.{indexCounter}");

                return(new ExecutionError(error.CodeInfo, $"{instanceType.Name}.{updatedInternalSource}", error.Message));
            });

            foreach (var executionError in nestedExecutionErrors)
            {
                outputResultToUpdate.AddError(executionError);
            }
        }
        /// <summary>
        /// Extension that fills sources for general error + internal error if exist.
        /// </summary>
        /// <typeparam name="TCommand">Type of command.</typeparam>
        /// <param name="executionResult">Failed Execution Result.</param>
        /// <param name="getProprty">Get property expression.</param>
        /// <returns>Filed FailedResult with correct Source.</returns>
        public static IExecutionResult ForCommand <TCommand>(this IExecutionResult executionResult, Expression <Func <TCommand, object> > getProprty)
            where TCommand : ICommand
        {
            if (!(executionResult is FailedResult failedResult))
            {
                return(executionResult);
            }

            if (failedResult.Details.Count == 0)
            {
                var source = SourceBuilder.BuildErrorSource(getProprty);
                return(new FailedResult(failedResult.CodeInfo, source, failedResult.Message));
            }

            var result = new FailedResult(failedResult.CodeInfo, typeof(TCommand).Name, failedResult.Message);

            var internalError    = failedResult.Details.First();
            var internalSource   = SourceBuilder.BuildErrorSource(getProprty);
            var newInternalError = new ExecutionError(internalError.CodeInfo, internalSource, internalError.Message);

            result.AddError(newInternalError);

            return(result);
        }