Skip to content

Commit

Permalink
Implement Custom Validation Messages With localization support
Browse files Browse the repository at this point in the history
  • Loading branch information
Alhiane committed Sep 26, 2024
1 parent c7a935d commit b6ad33b
Show file tree
Hide file tree
Showing 24 changed files with 300 additions and 81 deletions.
7 changes: 6 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PackageDescription

let package = Package(
name: "ValidatorKit",
defaultLocalization: "en",
platforms: [
.iOS(.v13),
.macOS(.v10_15),
Expand All @@ -17,7 +18,11 @@ let package = Package(
],
targets: [
.target(
name: "ValidatorKit"),
name: "ValidatorKit",
resources: [
.process("Resources/Localization")
]
),
.testTarget(
name: "ValidatorKitTests",
dependencies: ["ValidatorKit"]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// Localizable.strings
// ValidatorKit
//
// Created by Alhiane on 26/9/2024.
//

/* Localizable.strings (Arabic) */
"validation.custom" = "فشلت عملية التحقق.";
"validation.email" = "يرجى إدخال عنوان بريد إلكتروني صالح.";
"validation.min" = "يجب أن تكون القيمة على الأقل %@.";
"validation.max" = "يجب ألا تتجاوز القيمة %@.";
"validation.numeric" = "يرجى إدخال قيمة رقمية.";
"validation.date" = "يرجى إدخال تاريخ صالح.";
"validation.range" = "يجب أن تكون القيمة بين %@ و %@.";
"validation.pattern" = "القيمة لا تتطابق مع النموذج المطلوب.";
"validation.url" = "يرجى إدخال عنوان URL صالح.";
"validation.inArray" = "يجب أن تكون القيمة واحدة من الخيارات المسموح بها.";
"validation.requiredIf" = "هذا الحقل مطلوب بناءً على الشرط المحدد.";
"validation.required" = "هذا الحقل مطلوب.";
"validation.greaterThan" = "يجب أن تكون القيمة أكبر من %@.";
"validation.lessThan" = "يجب أن تكون القيمة أقل من %@.";
"validation.mimeTypes" = "يجب أن يكون نوع الملف واحدًا من الأنواع المسموح بها: %@.";

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// Localizable.strings
// ValidatorKit
//
// Created by Alhiane on 26/9/2024.
//

/* Localizable.strings (English) */
"validation.custom" = "Validation failed.";
"validation.email" = "Please enter a valid email address.";
"validation.min" = "Value must be at least %@.";
"validation.max" = "Value must be no more than %@.";
"validation.numeric" = "Please enter a numeric value.";
"validation.date" = "Please enter a valid date.";
"validation.range" = "Value must be between %@ and %@.";
"validation.pattern" = "Value does not match the required pattern.";
"validation.url" = "Please enter a valid URL.";
"validation.inArray" = "Value must be one of the allowed options.";
"validation.requiredIf" = "This field is required based on the specified condition.";
"validation.required" = "This field is required.";
"validation.greaterThan" = "Value must be greater than %@.";
"validation.lessThan" = "Value must be less than %@.";
"validation.mimeTypes" = "File type must be one of the allowed types: %@.";

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Localizable.strings
// ValidatorKit
//
// Created by Alhiane on 26/9/2024.
//

/* Localizable.strings (Spanish) */
"validation.custom" = "La validación falló.";
"validation.email" = "Por favor, introduce una dirección de correo electrónico válida.";
"validation.min" = "El valor debe ser al menos %@.";
"validation.max" = "El valor no debe exceder %@.";
"validation.numeric" = "Por favor, introduce un valor numérico.";
"validation.date" = "Por favor, introduce una fecha válida.";
"validation.range" = "El valor debe estar entre %@ y %@.";
"validation.pattern" = "El valor no coincide con el patrón requerido.";
"validation.url" = "Por favor, introduce una URL válida.";
"validation.inArray" = "El valor debe ser una de las opciones permitidas.";
"validation.requiredIf" = "Este campo es obligatorio según la condición especificada.";
"validation.required" = "Este campo es obligatorio.";
"validation.greaterThan" = "El valor debe ser mayor que %@.";
"validation.lessThan" = "El valor debe ser menor que %@.";
"validation.mimeTypes" = "El tipo de archivo debe ser uno de los tipos permitidos: %@.";
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// Localizable.strings
// ValidatorKit
//
// Created by Alhiane on 26/9/2024.
//

/* Localizable.strings (French) */
"validation.custom" = "La validation a échoué.";
"validation.email" = "Veuillez saisir une adresse e-mail valide.";
"validation.min" = "La valeur doit être au moins %@.";
"validation.max" = "La valeur ne doit pas dépasser %@.";
"validation.numeric" = "Veuillez saisir une valeur numérique.";
"validation.date" = "Veuillez saisir une date valide.";
"validation.range" = "La valeur doit être entre %@ et %@.";
"validation.pattern" = "La valeur ne correspond pas au modèle requis.";
"validation.url" = "Veuillez saisir une URL valide.";
"validation.inArray" = "La valeur doit être l'une des options autorisées.";
"validation.requiredIf" = "Ce champ est obligatoire en fonction de la condition spécifiée.";
"validation.required" = "Ce champ est obligatoire.";
"validation.greaterThan" = "La valeur doit être supérieure à %@.";
"validation.lessThan" = "La valeur doit être inférieure à %@.";
"validation.mimeTypes" = "Le type de fichier doit être l'un des types autorisés : %@.";

2 changes: 2 additions & 0 deletions Sources/ValidatorKit/Rules/AnyValidationRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
//
public struct AnyValidationRule: ValidationRule {
private let _validate: (Any?) -> ValidationError?
public let message: String

public init<R: ValidationRule>(_ rule: R) {
_validate = rule.validate
self.message = rule.message
}

public func validate(_ value: Any?) -> ValidationError? {
Expand Down
8 changes: 6 additions & 2 deletions Sources/ValidatorKit/Rules/DateRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@
import Foundation

public struct DateRule: ValidationRule {
public init() {}
public let message: String

public init(message: String? = nil) {
self.message = message ?? ValidationMessage.message(for: ValidationMessage.dateKey, defaultMessage: ValidationMessage.date)
}

public func validate(_ value: Any?) -> ValidationError? {
if !(value is Date) {
return ValidationError(message: "Invalid date")
return ValidationError(message: message)
}
return nil
}
Expand Down
11 changes: 8 additions & 3 deletions Sources/ValidatorKit/Rules/EmailRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,23 @@
import Foundation

public struct EmailRule: ValidationRule {
public init() {}
public let message: String

public init(message: String? = nil) {
self.message = message ?? ValidationMessage.message(for: ValidationMessage.emailKey, defaultMessage: ValidationMessage.email)

}

public func validate(_ value: Any?) -> ValidationError? {
guard let email = value as? String else {
return ValidationError(message: "Invalid email format")
return ValidationError(message: message)
}

let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPredicate = NSPredicate(format:"SELF MATCHES %@", emailRegex)

guard emailPredicate.evaluate(with: email) else {
return ValidationError(message: "Invalid email format")
return ValidationError(message: message)
}

return nil
Expand Down
8 changes: 5 additions & 3 deletions Sources/ValidatorKit/Rules/FileExtensionsRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,20 @@

public struct FileExtensionsRule: ValidationRule {
private let allowedExtensions: [String]
public let message: String

public init(allowedExtensions: [String]) {
public init(allowedExtensions: [String], message: String? = nil) {
self.allowedExtensions = allowedExtensions
self.message = message ?? ValidationMessage.message(for: ValidationMessage.mimeTypesKey, defaultMessage: ValidationMessage.mimeTypes)
}

public func validate(_ value: Any?) -> ValidationError? {
guard let filename = value as? String, let fileExtension = filename.split(separator: ".").last?.lowercased() else {
return ValidationError(message: "Invalid file")
return ValidationError(message: ValidationMessage.message(for: ValidationMessage.invalidFileKey, defaultMessage: ValidationMessage.invalidFile))
}

if !allowedExtensions.contains(fileExtension) {
return ValidationError(message: "File extension not allowed")
return ValidationError(message: message)
}
return nil
}
Expand Down
10 changes: 6 additions & 4 deletions Sources/ValidatorKit/Rules/GreaterThanRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
// Created by Alhiane on 23/9/2024.
//
public struct GreaterThanRule: ValidationRule {
public let message: String
private let minValue: Double

public init(minValue: Double) {
self.minValue = minValue
public init(minValue: Double, message: String? = nil) {
self.minValue = minValue
self.message = message ?? ValidationMessage.message(for: ValidationMessage.greaterThanKey, defaultMessage: ValidationMessage.greaterThan,dynamicValues: [String(minValue)])
}

public func validate(_ value: Any?) -> ValidationError? {
guard let doubleValue = value as? Double else { return ValidationError(message: "Invalid value") }
guard let doubleValue = value as? Double else { return ValidationError(message: ValidationMessage.message(for: ValidationMessage.invalidValueKey, defaultMessage: ValidationMessage.invalidValue)) }
if doubleValue <= minValue {
return ValidationError(message: "Must be greater than \(minValue)")
return ValidationError(message: message)
}
return nil
}
Expand Down
9 changes: 6 additions & 3 deletions Sources/ValidatorKit/Rules/InArrayRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@
//
public struct InArrayRule: ValidationRule {
private let allowedValues: [Any]
public let message: String

public init(allowedValues: [Any]) {

public init(allowedValues: [Any],message: String? = nil) {
self.allowedValues = allowedValues
self.message = message ?? ValidationMessage.message(for: ValidationMessage.inArrayKey, defaultMessage: ValidationMessage.inArray)
}

public func validate(_ value: Any?) -> ValidationError? {
guard let value = value else { return ValidationError(message: "This field is required") }
guard let value = value else { return ValidationError(message: message) }
if !allowedValues.contains(where: { "\($0)" == "\(value)" }) {
return ValidationError(message: "Value is not in the allowed list")
return ValidationError(message: message)
}
return nil
}
Expand Down
8 changes: 5 additions & 3 deletions Sources/ValidatorKit/Rules/LessThanRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@

public struct LessThanRule: ValidationRule {
private let maxValue: Double
public let message: String

public init(maxValue: Double) {
public init(maxValue: Double, message: String? = nil) {
self.maxValue = maxValue
self.message = message ?? ValidationMessage.message(for: ValidationMessage.lessThanKey, defaultMessage: ValidationMessage.lessThan,dynamicValues: [String(maxValue)])
}

public func validate(_ value: Any?) -> ValidationError? {
guard let doubleValue = value as? Double else { return ValidationError(message: "Invalid value") }
guard let doubleValue = value as? Double else { return ValidationError(message: ValidationMessage.message(for: ValidationMessage.invalidValueKey, defaultMessage: ValidationMessage.invalidValue)) }
if doubleValue >= maxValue {
return ValidationError(message: "Must be less than \(maxValue)")
return ValidationError(message: message)
}
return nil
}
Expand Down
22 changes: 6 additions & 16 deletions Sources/ValidatorKit/Rules/MaxRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
public struct MaxRule: ValidationRule {
private let maxValue: Double
private let epsilon = 0.0001 // Precision handling for floating-point numbers
public let message: String

public init(value: Double) {
public init(value: Double, message: String? = nil) {
self.maxValue = value
self.message = message ?? ValidationMessage.message(for: ValidationMessage.maxKey, defaultMessage: ValidationMessage.max,dynamicValues: [String(value)])
}

public func validate(_ value: Any?) -> ValidationError? {
guard let value = value else {
return ValidationError(message: "Value cannot be nil")
return ValidationError(message: ValidationMessage.message(for: ValidationMessage.notNullKey, defaultMessage: ValidationMessage.notNull))
}

// Check if the value is a numeric string
Expand All @@ -31,25 +33,13 @@ public struct MaxRule: ValidationRule {
return validateNumeric(Double(intValue))
}

// Handle non-numeric strings (apply string length validation)
if let stringValue = value as? String {
return validateStringLength(stringValue)
}

// If the value doesn't match any expected type
return ValidationError(message: "Invalid value type")
return ValidationError(message: ValidationMessage.message(for: ValidationMessage.invalidTypeKey, defaultMessage: ValidationMessage.invalidType))
}

private func validateNumeric(_ number: Double) -> ValidationError? {
if number > maxValue + epsilon {
return ValidationError(message: "Value must be less than or equal to \(maxValue)")
}
return nil
}

private func validateStringLength(_ string: String) -> ValidationError? {
if string.count > Int(maxValue) {
return ValidationError(message: "Maximum length is \(Int(maxValue)) characters")
return ValidationError(message: message)
}
return nil
}
Expand Down
23 changes: 7 additions & 16 deletions Sources/ValidatorKit/Rules/MinRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ public struct MinRule: ValidationRule {
private let minValue: Double
private let epsilon = 0.0001 // Precision handling for floating-point numbers

public init(value: Double) {
public let message: String

public init(value: Double,message: String? = nil) {
self.minValue = value
self.message = message ?? ValidationMessage.message(for: ValidationMessage.minKey, defaultMessage: ValidationMessage.min,dynamicValues: [String(value)])
}

public func validate(_ value: Any?) -> ValidationError? {
guard let value = value else {
return ValidationError(message: "Value cannot be nil")
return ValidationError(message: ValidationMessage.message(for: ValidationMessage.notNullKey, defaultMessage: ValidationMessage.notNull))
}

// Check if the value is a numeric string
Expand All @@ -31,25 +34,13 @@ public struct MinRule: ValidationRule {
return validateNumeric(Double(intValue))
}

// Handle non-numeric strings (apply string length validation)
if let stringValue = value as? String {
return validateStringLength(stringValue)
}

// If the value doesn't match any expected type
return ValidationError(message: "Invalid value type")
return ValidationError(message: ValidationMessage.message(for: ValidationMessage.invalidTypeKey, defaultMessage: ValidationMessage.invalidType))
}

private func validateNumeric(_ number: Double) -> ValidationError? {
if number < minValue - epsilon {
return ValidationError(message: "Value must be greater than or equal to \(minValue)")
}
return nil
}

private func validateStringLength(_ string: String) -> ValidationError? {
if string.count < Int(minValue) {
return ValidationError(message: "Minimum length is \(Int(minValue)) characters")
return ValidationError(message: message)
}
return nil
}
Expand Down
9 changes: 6 additions & 3 deletions Sources/ValidatorKit/Rules/NumericRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
import Foundation

public struct NumericRule: ValidationRule {
public init() {}
public let message: String
public init(message: String? = nil) {
self.message = message ?? ValidationMessage.message(for: ValidationMessage.numericKey, defaultMessage: ValidationMessage.numeric)
}

public func validate(_ value: Any?) -> ValidationError? {
// Check if value is nil
guard let value = value else {
return ValidationError(message: "This field must be a number")
return ValidationError(message: message)
}

// Handle numeric types directly
Expand All @@ -30,6 +33,6 @@ public struct NumericRule: ValidationRule {
}

// If none of the above checks passed, return an error
return ValidationError(message: "This field must be a valid number")
return ValidationError(message: message)
}
}
Loading

0 comments on commit b6ad33b

Please sign in to comment.