예제 #1
0
        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);
            }
        }
예제 #2
0
        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)));
                }
            }
        }
예제 #3
0
        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");
            }
        }
예제 #4
0
        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));
        }
예제 #5
0
        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();
        }
예제 #6
0
 private void AddInputValueError(ModelErrorDictionary errors, string error, string templateStepInputId)
 {
     errors.Add(error, $"{nameof(DocumentCreate.InputValues)}[\"{templateStepInputId}\"]");
 }
예제 #7
0
        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);
        }
예제 #8
0
        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);
        }