Skip to content

Commit 1c32fd7

Browse files
authored
feat: add comparison validation rule (#86)
1 parent d863857 commit 1c32fd7

File tree

4 files changed

+143
-0
lines changed

4 files changed

+143
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ struct RegistrationView: View {
313313
| `NoWhitespaceValidationRule` | Validates that a string does not contain any whitespace characters | `NoWhitespaceValidationRule(error: "Spaces are not allowed")`
314314
| `ContainsValidationRule` | Validates that a string contains a specific substring | `ContainsValidationRule(substring: "@", error: "Must contain @")`
315315
| `EqualityValidationRule`| Validates that the input is equal to a given reference value | `EqualityValidationRule(compareTo: password, error: "Passwords do not match")`
316+
| `ComparisonValidationRule` | Validates that input against a comparison constraint | `ComparisonValidationRule(greaterThan: 0, error: "Must be greater than 0")`
316317

317318
## Custom Validators
318319

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//
2+
// Validator
3+
// Copyright © 2025 Space Code. All rights reserved.
4+
//
5+
6+
/// A validation rule that checks input against a comparison constraint.
7+
///
8+
/// Supports comparisons such as:
9+
/// - `>` (greater than)
10+
/// - `>=` (greater than or equal to)
11+
/// - `<` (less than)
12+
/// - `<=` (less than or equal to)
13+
///
14+
/// # Examples:
15+
/// ```swift
16+
/// let rule1 = ComparisonValidationRule(greaterThan: 0, error: "Must be greater than 0")
17+
/// rule1.validate(input: 5) // true
18+
/// rule1.validate(input: -1) // false
19+
///
20+
/// let rule2 = ComparisonValidationRule(lessThanOrEqual: 100, error: "Must be <= 100")
21+
/// rule2.validate(input: 100) // true
22+
/// rule2.validate(input: 150) // false
23+
/// ```
24+
public struct ComparisonValidationRule<T: Comparable>: IValidationRule {
25+
// MARK: - Types
26+
27+
public typealias Input = T
28+
29+
/// Supported comparison operators.
30+
public enum Condition {
31+
case greaterThan(T)
32+
case greaterThanOrEqual(T)
33+
case lessThan(T)
34+
case lessThanOrEqual(T)
35+
}
36+
37+
// MARK: - Properties
38+
39+
/// The comparison condition to evaluate.
40+
public let condition: Condition
41+
42+
/// The validation error returned when validation fails.
43+
public let error: IValidationError
44+
45+
// MARK: - Initialization
46+
47+
/// Creates a comparison-based validation rule.
48+
///
49+
/// - Parameters:
50+
/// - condition: The comparison condition.
51+
/// - error: The validation error returned if validation fails.
52+
public init(condition: Condition, error: IValidationError) {
53+
self.condition = condition
54+
self.error = error
55+
}
56+
57+
/// Convenience initializer for `greaterThan`.
58+
public init(greaterThan value: T, error: IValidationError) {
59+
self.init(condition: .greaterThan(value), error: error)
60+
}
61+
62+
/// Convenience initializer for `greaterThanOrEqual`.
63+
public init(greaterThanOrEqual value: T, error: IValidationError) {
64+
self.init(condition: .greaterThanOrEqual(value), error: error)
65+
}
66+
67+
/// Convenience initializer for `lessThan`.
68+
public init(lessThan value: T, error: IValidationError) {
69+
self.init(condition: .lessThan(value), error: error)
70+
}
71+
72+
/// Convenience initializer for `lessThanOrEqual`.
73+
public init(lessThanOrEqual value: T, error: IValidationError) {
74+
self.init(condition: .lessThanOrEqual(value), error: error)
75+
}
76+
77+
// MARK: - IValidationRule
78+
79+
public func validate(input: T) -> Bool {
80+
switch condition {
81+
case let .greaterThan(value):
82+
input > value
83+
case let .greaterThanOrEqual(value):
84+
input >= value
85+
case let .lessThan(value):
86+
input < value
87+
case let .lessThanOrEqual(value):
88+
input <= value
89+
}
90+
}
91+
}

Sources/ValidatorCore/Validator.docc/Overview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ ValidatorCore contains all core validation rules, utilities, and mechanisms for
3636
- ``NoWhitespaceValidationRuleTests``
3737
- ``ContainsValidationRule``
3838
- ``EqualityValidationRule``
39+
- ``ComparisonValidationRule``
3940

4041
### Articles
4142

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// Validator
3+
// Copyright © 2025 Space Code. All rights reserved.
4+
//
5+
6+
@testable import ValidatorCore
7+
import XCTest
8+
9+
// MARK: - ComparisonValidationRuleTests
10+
11+
final class ComparisonValidationRuleTests: XCTestCase {
12+
func test_greaterThanRule_passesForValidValue() {
13+
let sut = ComparisonValidationRule(greaterThan: 10, error: String.error)
14+
XCTAssertTrue(sut.validate(input: 20))
15+
}
16+
17+
func test_greaterThanRule_failsForInvalidValue() {
18+
let sut = ComparisonValidationRule(greaterThan: 10, error: String.error)
19+
XCTAssertFalse(sut.validate(input: 10))
20+
}
21+
22+
func test_greaterThanOrEqualRule_allowsEqualValue() {
23+
let sut = ComparisonValidationRule(greaterThanOrEqual: 10, error: String.error)
24+
XCTAssertTrue(sut.validate(input: 10))
25+
}
26+
27+
func test_lessThanRule_passesForSmallerValues() {
28+
let sut = ComparisonValidationRule(lessThan: 100, error: String.error)
29+
XCTAssertTrue(sut.validate(input: 50))
30+
XCTAssertFalse(sut.validate(input: 100))
31+
}
32+
33+
func test_lessThanOrEqualRule_allowsEqualValue() {
34+
let sut = ComparisonValidationRule(lessThanOrEqual: 100, error: String.error)
35+
XCTAssertTrue(sut.validate(input: 100))
36+
XCTAssertFalse(sut.validate(input: 101))
37+
}
38+
39+
func test_ruleWorksWithStringComparable() {
40+
let sut = ComparisonValidationRule(lessThan: "m", error: String.error)
41+
XCTAssertTrue(sut.validate(input: "apple"))
42+
XCTAssertFalse(sut.validate(input: "z"))
43+
}
44+
}
45+
46+
// MARK: Constants
47+
48+
private extension String {
49+
static let error = "Invalid characters"
50+
}

0 commit comments

Comments
 (0)