private void ValidateTemplateMarkup(Template template, IEnumerable <TemplateErrorSuppression> errorSuppressions) { var references = template.Steps.SelectMany(s => s.Inputs.Select(i => { var inputId = s.Id; if (!string.IsNullOrEmpty(i.Key)) { inputId += $".{i.Key}"; } if (i.Type == TemplateStepInputType.Text) { return(ReferenceDefinition.String(inputId)); } else if (i.Type == TemplateStepInputType.Checkbox) { return(ReferenceDefinition.Boolean(inputId)); } else if (i.Type == TemplateStepInputType.Radio) { var values = DynamicUtility.Unwrap(() => (IEnumerable <dynamic>)i.TypeData) .Select(d => DynamicUtility.Unwrap <string>(() => d.Value)); return(ReferenceDefinition.StringFrom(inputId, values)); } else { throw new Exception($"Unknown {nameof(TemplateStepInputType)}"); } })); _templateMarkupValidator.Validate(template.Markup, template.MarkupVersion, references, errorSuppressions); }
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 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); }