// // FormFieldable.swift // VDS // // Created by Matt Bruce on 7/22/22. // import Foundation /// Protocol used for a FormField object. public protocol FormFieldable { associatedtype ValueType = AnyHashable /// Unique Id for the Form Field object within a Form. var inputId: String? { get set } /// Value for the Form Field. var value: ValueType? { get } } /// Protocol for FormFieldable that require internal validation. public protocol FormFieldInternalValidatable: FormFieldable, Errorable { /// Rules that drive the validator var rules: [AnyRule] { get set } /// Is there an internalError var hasInternalError: Bool { get } /// Internal Error Message that will show. var internalErrorText: String? { get } var validator: (any FormFieldValidatorable)? { get set } func validate() } extension FormFieldInternalValidatable { /// Whether or not to show the internal error public var hasInternalError: Bool { guard let validator, !showError else { return false } return !validator.isValid } public var internalErrorText: String? { guard let validator, !validator.isValid else { return nil } if let errorText, !errorText.isEmpty { return errorText } else { return validator.errorMessage } } } /// Struct that will execute the validation. public protocol FormFieldValidatorable { associatedtype FieldType: FormFieldable /// FormFieldable to be validated. var field: FieldType { get set } /// Rules that will be applied against the FormFieldable var rules: [AnyRule] { get set } /// Error Message that will show. var errorMessage: String? { get } /// Is the FormField valid. var isValid: Bool { get } /// Run the rules against the FormFieldable. func validate() } /// Rule that will be executed against a specific ValueType. public protocol Rule { associatedtype ValueType /// Determines if this rule valid for the value passed. func isValid(value: ValueType?) -> Bool /// Error Message to be show if the value is invalid. var errorMessage: String { get } /// type of rule var ruleType: String { get } } extension Rule { public var ruleType: String { "\(Self.self)" } } /// Type Erased Rule for a specific ValueType. public struct AnyRule: Rule { private let _isValid: (ValueType?) -> Bool public var ruleType: String public let errorMessage: String public init(_ rule: R) where R.ValueType == ValueType { self._isValid = rule.isValid self.ruleType = rule.ruleType self.errorMessage = rule.errorMessage } public func isValid(value: ValueType?) -> Bool { return _isValid(value) } } /// Generic Validator for a specific FormFieldable. public class FormFieldValidator: FormFieldValidatorable{ public var field: Field public var rules: [AnyRule] public var errorMessages = [String]() public var isValid: Bool = true public init(field: Field, rules: [AnyRule]) { self.field = field self.rules = rules } public var errorMessage: String? { guard errorMessages.count > 0 else { return nil } return errorMessages.joined(separator: "\r") } public func validate() { errorMessages.removeAll() for rule in rules { if !rule.isValid(value: field.value) { errorMessages.append(rule.errorMessage) isValid = false return } } isValid = true } }