Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ struct RegistrationView: View {
| `NoWhitespaceValidationRule` | Validates that a string does not contain any whitespace characters | `NoWhitespaceValidationRule(error: "Spaces are not allowed")`
| `ContainsValidationRule` | Validates that a string contains a specific substring | `ContainsValidationRule(substring: "@", error: "Must contain @")`
| `EqualityValidationRule`| Validates that the input is equal to a given reference value | `EqualityValidationRule(compareTo: password, error: "Passwords do not match")`
| `ComparisonValidationRule` | Validates that input against a comparison constraint | `ComparisonValidationRule(greaterThan: 0, error: "Must be greater than 0")`

## Custom Validators

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//
// Validator
// Copyright © 2025 Space Code. All rights reserved.
//

/// A validation rule that checks input against a comparison constraint.
///
/// Supports comparisons such as:
/// - `>` (greater than)
/// - `>=` (greater than or equal to)
/// - `<` (less than)
/// - `<=` (less than or equal to)
///
/// # Examples:
/// ```swift
/// let rule1 = ComparisonValidationRule(greaterThan: 0, error: "Must be greater than 0")
/// rule1.validate(input: 5) // true
/// rule1.validate(input: -1) // false
///
/// let rule2 = ComparisonValidationRule(lessThanOrEqual: 100, error: "Must be <= 100")
/// rule2.validate(input: 100) // true
/// rule2.validate(input: 150) // false
/// ```
public struct ComparisonValidationRule<T: Comparable>: IValidationRule {
// MARK: - Types

public typealias Input = T

/// Supported comparison operators.
public enum Condition {
case greaterThan(T)
case greaterThanOrEqual(T)
case lessThan(T)
case lessThanOrEqual(T)
}

// MARK: - Properties

/// The comparison condition to evaluate.
public let condition: Condition

/// The validation error returned when validation fails.
public let error: IValidationError

// MARK: - Initialization

/// Creates a comparison-based validation rule.
///
/// - Parameters:
/// - condition: The comparison condition.
/// - error: The validation error returned if validation fails.
public init(condition: Condition, error: IValidationError) {
self.condition = condition
self.error = error
}

/// Convenience initializer for `greaterThan`.
public init(greaterThan value: T, error: IValidationError) {
self.init(condition: .greaterThan(value), error: error)
}

/// Convenience initializer for `greaterThanOrEqual`.
public init(greaterThanOrEqual value: T, error: IValidationError) {
self.init(condition: .greaterThanOrEqual(value), error: error)
}

/// Convenience initializer for `lessThan`.
public init(lessThan value: T, error: IValidationError) {
self.init(condition: .lessThan(value), error: error)
}

/// Convenience initializer for `lessThanOrEqual`.
public init(lessThanOrEqual value: T, error: IValidationError) {
self.init(condition: .lessThanOrEqual(value), error: error)
}

// MARK: - IValidationRule

public func validate(input: T) -> Bool {
switch condition {
case let .greaterThan(value):
input > value
case let .greaterThanOrEqual(value):
input >= value
case let .lessThan(value):
input < value
case let .lessThanOrEqual(value):
input <= value
}
}
}
1 change: 1 addition & 0 deletions Sources/ValidatorCore/Validator.docc/Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ ValidatorCore contains all core validation rules, utilities, and mechanisms for
- ``NoWhitespaceValidationRuleTests``
- ``ContainsValidationRule``
- ``EqualityValidationRule``
- ``ComparisonValidationRule``

### Articles

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// Validator
// Copyright © 2025 Space Code. All rights reserved.
//

@testable import ValidatorCore
import XCTest

// MARK: - ComparisonValidationRuleTests

final class ComparisonValidationRuleTests: XCTestCase {
func test_greaterThanRule_passesForValidValue() {
let sut = ComparisonValidationRule(greaterThan: 10, error: String.error)
XCTAssertTrue(sut.validate(input: 20))
}

func test_greaterThanRule_failsForInvalidValue() {
let sut = ComparisonValidationRule(greaterThan: 10, error: String.error)
XCTAssertFalse(sut.validate(input: 10))
}

func test_greaterThanOrEqualRule_allowsEqualValue() {
let sut = ComparisonValidationRule(greaterThanOrEqual: 10, error: String.error)
XCTAssertTrue(sut.validate(input: 10))
}

func test_lessThanRule_passesForSmallerValues() {
let sut = ComparisonValidationRule(lessThan: 100, error: String.error)
XCTAssertTrue(sut.validate(input: 50))
XCTAssertFalse(sut.validate(input: 100))
}

func test_lessThanOrEqualRule_allowsEqualValue() {
let sut = ComparisonValidationRule(lessThanOrEqual: 100, error: String.error)
XCTAssertTrue(sut.validate(input: 100))
XCTAssertFalse(sut.validate(input: 101))
}

func test_ruleWorksWithStringComparable() {
let sut = ComparisonValidationRule(lessThan: "m", error: String.error)
XCTAssertTrue(sut.validate(input: "apple"))
XCTAssertFalse(sut.validate(input: "z"))
}
}

// MARK: Constants

private extension String {
static let error = "Invalid characters"
}