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); }
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); }