private void ValidateTemplateSteps(TemplateCreate create, IEnumerable <TemplateStepCreate> steps) { steps .Where(step => step.ParentReference?.Split(Constants.TemplateComponentReferenceSeparator).Count() > 1) .ForEach((step) => { throw new NotImplementedException("Only one level of step nesting is supported"); }); var stepsByIdLookup = steps.ToIndexedLookup(s => s.Id); stepsByIdLookup .Where(g => g.Count() > 1) .Select(g => g.Skip(1).First()) .ForEach(indexedStep => { throw new ClientModelValidationException( $"A step with the name {indexedStep.Element.Name} already exists (case-insensitive)", $"{nameof(TemplateCreate.Steps)}[{indexedStep.Index}]"); // TODO: Make exception take params of object and resolve member name }); var stepErrors = new ModelErrorDictionary(); var stepsById = stepsByIdLookup.ToDictionary(g => g.Key, g => g.Single()); stepsById.ForEach(kvp => ValidateTemplateStep(create, kvp.Key, stepsById, stepErrors)); if (stepErrors.HasErrors) { throw new ClientModelValidationException(stepErrors); } }
private void ValidateTemplateStepInput(TemplateStepInputCreate stepInput, ModelErrorDictionary stepErrors, IEnumerable <object> stepInputErrorPath, bool isOnlyInput) { if (isOnlyInput) { if (!string.IsNullOrEmpty(stepInput.Name)) { stepErrors.Add("Must be empty if there is only one input for the step", stepInputErrorPath.Concat(nameof(TemplateStepInputCreate.Name))); } if (!string.IsNullOrEmpty(stepInput.Description)) { stepErrors.Add("Must be empty if there is only one input for the step", stepInputErrorPath.Concat(nameof(TemplateStepInputCreate.Description))); } } else { if (string.IsNullOrEmpty(stepInput.Key)) { stepErrors.Add("Required if there is more than one input for the step", stepInputErrorPath.Concat(nameof(TemplateStepInputCreate.Key))); } if (string.IsNullOrEmpty(stepInput.Name)) { stepErrors.Add("Required if there is more than one input for the step", stepInputErrorPath.Concat(nameof(TemplateStepInputCreate.Name))); } if (string.IsNullOrEmpty(stepInput.Description)) { stepErrors.Add("Required if there is more than one input for the step", stepInputErrorPath.Concat(nameof(TemplateStepInputCreate.Description))); } } }
public static void Validate(this CustomerDto customer, ModelStateDictionary modelState) { var errors = new ModelErrorDictionary<CustomerDto>(modelState); if (customer.CustomerID.Trim().Length != 5) { errors.AddModelError(c => c.CustomerID, "Die CustomerID muss 5 Zeichen lang sein"); } }
private void ValidateTemplateStepInputs(TemplateStepCreate step, ModelErrorDictionary stepErrors, IEnumerable <object> stepErrorPath) { var stepInputsErrorPath = stepErrorPath.Concat(nameof(TemplateStepCreate.Inputs)); step.Inputs .ToIndexedLookup(i => i.Key) .Where(g => g.Count() > 1) .SelectMany(g => g.AsEnumerable()) .ForEach(indexedStepInput => { stepErrors.Add( $"An input with the id {indexedStepInput.Element.Key} already exists in this step (case-insensitive)", stepInputsErrorPath.Concat(new object[] { indexedStepInput.Index })); }); var isOnlyInput = step.Inputs.Count() == 1; step.Inputs.ForEach((stepInput, stepInputIndex) => ValidateTemplateStepInput( stepInput, stepErrors, stepInputsErrorPath.Concat(stepInputIndex), isOnlyInput)); }
private void ValidateDocumentAgainstTemplate(DocumentCreate create, Template template) { var inputValueErrors = new ModelErrorDictionary(); // Store the traversed values so we can easily analyse conditional inputs. It's fine to just store them as strings as we // have already validated their types and value comparison will work for stringified booleans and numbers. var validInputStringValues = new Dictionary <string, string>(); template.Steps.ForEach(templateStep => { var allConditionsMet = templateStep.Conditions.All(condition => { if (condition.Type == TemplateComponentConditionType.EqualsPreviousInputValue) { var expectedPreviousInputValue = DynamicUtility.Unwrap <string>(() => condition.TypeData.PreviousInputValue); var previousInputId = DynamicUtility.Unwrap <string>(() => condition.TypeData.PreviousInputId); if (string.IsNullOrEmpty(expectedPreviousInputValue) || string.IsNullOrEmpty(previousInputId)) { throw new Exception("Internal error: invalid template"); } return(validInputStringValues.ContainsKey(previousInputId) && expectedPreviousInputValue.Equals(validInputStringValues[previousInputId])); } else if (condition.Type == TemplateComponentConditionType.IsDocumentSigned) { // Skip this step if the contract won't be signed return(create.GetIsSigned()); } return(false); }); if (!allConditionsMet) { return; } var templateStepId = templateStep.Id; var defaultTemplateStepInput = templateStep.Inputs.SingleOrDefault(i => string.IsNullOrEmpty(i.Key)); if (defaultTemplateStepInput != null) { var result = ValidateTemplateStepInput(templateStep, defaultTemplateStepInput, create.InputValues, inputValueErrors, isDefaultInput: true); if (result.success) { validInputStringValues.Add(result.templateStepInputId, result.inputValueString); } } templateStep.Inputs .Where(templateStepInput => !string.IsNullOrEmpty(templateStepInput.Key)) .ForEach(templateStepInput => { var result = ValidateTemplateStepInput(templateStep, templateStepInput, create.InputValues, inputValueErrors, isDefaultInput: false); if (result.success) { validInputStringValues.Add(result.templateStepInputId, result.inputValueString); } }); }); inputValueErrors.AssertValid(); }
private void AddInputValueError(ModelErrorDictionary errors, string error, string templateStepInputId) { errors.Add(error, $"{nameof(DocumentCreate.InputValues)}[\"{templateStepInputId}\"]"); }
private (bool success, string templateStepInputId, string inputValueString) ValidateTemplateStepInput( TemplateStep templateStep, TemplateStepInput templateStepInput, IDictionary <string, dynamic> inputValues, ModelErrorDictionary errors, bool isDefaultInput) { var templateStepInputId = templateStep.Id; if (!isDefaultInput) { templateStepInputId += Templates.Constants.TemplateComponentReferenceSeparator + templateStepInput.Key; } if (inputValues.TryGetValue(templateStepInputId, out dynamic inputValueDynamic)) // Id is a reference to a step { if (templateStepInput.Type == TemplateStepInputType.Text) { try { var textValue = (string)inputValueDynamic; return(true, templateStepInputId, textValue); } catch (RuntimeBinderException) { AddInputValueError(errors, $"Expected string for \"{templateStepInput.Name ?? templateStep.Name}\"", templateStepInputId); } } else if (templateStepInput.Type == TemplateStepInputType.Radio) { try { var radioValue = (string)inputValueDynamic; try { var radioOptions = (IEnumerable <dynamic>)(templateStepInput.TypeData); if (radioOptions.Any(r => r.Value == radioValue)) { return(true, templateStepInputId, inputValueDynamic); } else { AddInputValueError(errors, $"Unexpected value; must be from the set of values {string.Join(", ", radioOptions.Select(r => $"\"{r.Value}\""))}", templateStepInputId); } } catch (RuntimeBinderException ex) { throw new Exception("Internal error: invalid template", ex); } } catch (RuntimeBinderException) { AddInputValueError(errors, $"Expected string for \"{templateStepInput.Name ?? templateStep.Name}\"", templateStepInputId); } } else if (templateStepInput.Type == TemplateStepInputType.Checkbox) { try { var checkboxValue = (bool)(bool.Parse(inputValueDynamic)); return(true, templateStepInputId, checkboxValue.ToString()); } catch (RuntimeBinderException) { AddInputValueError(errors, $"Expected boolean for \"{templateStepInput.Name ?? templateStep.Name}\"", templateStepInputId); } } } else { AddInputValueError(errors, $"Input \"{templateStepInput.Name ?? templateStep.Name}\" is required", templateStepInputId); } return(false, templateStepInputId, null); }
private void ValidateTemplateStep(TemplateCreate create, string stepId, Dictionary <string, IndexedElement <TemplateStepCreate> > stepsById, ModelErrorDictionary stepErrors) { var indexedStep = stepsById[stepId]; var step = indexedStep.Element; var stepIndex = indexedStep.Index; var stepErrorPath = new object[] { nameof(TemplateCreate.Steps), stepIndex }; var isParentStep = stepsById.Any(kvp => kvp.Key.StartsWith(stepId) && kvp.Value.Index != stepIndex); if (!isParentStep && !step.Inputs.Any()) { // Steps that do not have child steps must have inputs. // FUTURE: Maybe we want to add "informational" steps, with no inputs. stepErrors.Add("Step must have at least one inputs", stepErrorPath); } var stepConditionsErrorPath = stepErrorPath.Concat(nameof(TemplateStepCreate.Conditions)); step.Conditions.ForEach((condition, conditionIndex) => { var stepConditionErrorPath = stepConditionsErrorPath.Concat(conditionIndex); var stepConditionTypeErrorPath = stepConditionErrorPath.Concat(nameof(TemplateStepCondition.Type)); var stepConditionTypeDataErrorPath = stepConditionErrorPath.Concat(nameof(TemplateStepCondition.TypeData)); if (condition.Type == TemplateComponentConditionType.EqualsPreviousInputValue) { var previousInputIdErrorPath = stepConditionTypeDataErrorPath.Concat(nameof(TemplateStepConditionTypeData_EqualsPreviousInputValue.PreviousInputId)); var previousInputValueErrorPath = stepConditionTypeDataErrorPath.Concat(nameof(TemplateStepConditionTypeData_EqualsPreviousInputValue.PreviousInputValue)); var previousInputId = DynamicUtility.Unwrap <string>(() => condition.TypeData.PreviousInputId); var(previousInputSuccess, previousInputError, previousInput) = GetPreviousInputReference(stepId, previousInputId, stepsById); if (previousInputSuccess) { if (previousInput.Type == TemplateStepInputType.Checkbox) { try { DynamicUtility.UnwrapValue <bool>(() => condition.TypeData.PreviousInputValue); } catch (RuntimeBinderException) { stepErrors.Add("Expected boolean", previousInputValueErrorPath); } } else if (previousInput.Type == TemplateStepInputType.Radio) { try { DynamicUtility.Unwrap <string>(() => condition.TypeData.PreviousInputValue); } catch (RuntimeBinderException) { throw; } } else { stepErrors.Add("Type of previous input is not supported for conditions", previousInputValueErrorPath); } } else { stepErrors.Add(previousInputError, previousInputIdErrorPath); } } else if (condition.Type == TemplateComponentConditionType.IsDocumentSigned) { if (!create.IsSignable) { stepErrors.Add("Step condition cannot be on document signing if the template does not allow signing", stepConditionTypeErrorPath); } } }); ValidateTemplateStepInputs(step, stepErrors, stepErrorPath); }