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
76 changes: 76 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Deploy DocC Documentation

on:
push:
branches: ["main"]
workflow_dispatch:
inputs:
version:
description: 'Documentation version'
required: false
default: latest'

permissions:
contents: write

concurrency:
group: "pages"
cancel-in-progress: true

jobs:
deploy:
runs-on: macos-14
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable

- name: Get Version
id: version
run: |
if [ -n "${{ github.event.inputs.version }}" ]; then
VERSION="${{ github.event.inputs.version }}"
else
VERSION="latest"
fi
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "Building documentation for version: $VERSION"

- name: Build DocC
id: build
uses: space-code/build-docc@main
with:
schemes: '["ValidatorCore", "ValidatorUI"]'
version: ${{ steps.version.outputs.version }}

- name: Generate Index Page
uses: space-code/generate-index@v1.0.0
with:
version: ${{ steps.version.outputs.version }}
project-name: 'Validator'
project-description: 'Validator is a modern, lightweight Swift framework that provides elegant and type-safe input validation.'
modules: |
[
{
"name": "ValidatorCore",
"path": "validatorcore",
"description": "Core validation functionality and rules for Swift applications. Contains base types, protocols, and validator implementations.",
"badge": "Core Module"
},
{
"name": "ValidatorUI",
"path": "validatorui",
"description": "UI components and helpers for building validation interfaces. Ready-to-use solutions for SwiftUI and UIKit.",
"badge": "UI Module"
}
]

- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@

import Foundation

/// `IValidationError` is the error type returned by Validator.
/// `IValidationError` is the protocol representing a validation error in `ValidatorCore`.
/// Any type conforming to `IValidationError` can be returned when validation fails.
///
/// Example usage:
/// ```swift
/// struct MyError: IValidationError {
/// var message: String { "Invalid input" }
/// }
/// ```
public protocol IValidationError: Error {
/// The error message.
/// A human-readable error message describing why validation failed.
var message: String { get }
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,30 @@

import Foundation

/// A type that verifies the input data.
/// `IValidationRule` is a generic protocol representing a validation rule.
/// Types conforming to this protocol define the logic for validating input values of type `Input`.
///
/// You can create custom validators by implementing this protocol.
///
/// Example:
/// ```swift
/// struct NonEmptyRule: IValidationRule<String> {
/// let error = "Field cannot be empty"
/// func validate(input: String) -> Bool {
/// !input.isEmpty
/// }
/// }
/// ```
public protocol IValidationRule<Input> {
/// The validation type.
/// The type of input this validator works with.
associatedtype Input

/// The validation error.
/// The validation error that will be returned if validation fails.
var error: IValidationError { get }

/// Validates an input value.
/// Validates the provided input.
///
/// - Parameter input: The input value.
///
/// - Returns: A validation result.
/// - Parameter input: The value to validate.
/// - Returns: `true` if the input passes validation, `false` otherwise.
func validate(input: Input) -> Bool
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@

import Foundation

/// `ValidationResult` represents the outcome of a validation operation.
/// It can either be `.valid` when all checks pass or `.invalid` with a list of errors.
public enum ValidationResult {
/// Indicates that the validation was successful.
/// Indicates that validation succeeded.
case valid

/// Indicates that the validation failed with a list of errors.
/// Indicates that validation failed.
///
/// - Parameter errors: An array of validation errors.
/// - Parameter errors: An array of `IValidationError` instances describing each failure.
case invalid(errors: [IValidationError])
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,21 @@
import Foundation

#if hasFeature(RetroactiveAttribute)
/// Retroactive conformance to `Error` for `String` if the feature is enabled.
extension String: @retroactive Error {}
#endif

// MARK: - String + IValidationError

/// Makes `String` conform to `IValidationError`.
/// This allows simple string literals to be used as validation errors without creating a custom type.
///
/// Example:
/// ```swift
/// let error: IValidationError = "Invalid input"
/// print(error.message) // "Invalid input"
/// ```
extension String: IValidationError {
/// Returns the string itself as the error message.
public var message: String { self }
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,23 @@

import Foundation

/// Adds `Equatable` conformance to `ValidationResult`.
/// Validation results are considered equal if both are `.valid`
/// or if both are `.invalid` and all error messages match.
///
/// Example:
/// ```swift
/// let r1: ValidationResult = .invalid(errors: ["Error 1", "Error 2"])
/// let r2: ValidationResult = .invalid(errors: ["Error 1", "Error 2"])
/// print(r1 == r2) // true
/// ```
extension ValidationResult: Equatable {
public static func == (lhs: ValidationResult, rhs: ValidationResult) -> Bool {
switch (lhs, rhs) {
case (.valid, .valid):
true
case let (.invalid(errors: lhs), .invalid(errors: rhs)):
lhs.map(\.message).joined() == rhs.map(\.message).joined()
case let (.invalid(errors: lhsErrors), .invalid(errors: rhsErrors)):
lhsErrors.map(\.message).joined() == rhsErrors.map(\.message).joined()
default:
false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,34 @@

import Foundation

/// A characters validation rule.
/// Validates that a string contains only allowed characters.
///
/// # Example:
/// ```swift
/// let rule = CharactersValidationRule(characterSet: .letters, error: "Only letters allowed")
/// rule.validate(input: "Hello") // true
/// rule.validate(input: "Hello123") // false
/// ```
public struct CharactersValidationRule: IValidationRule {
// MARK: Types

public typealias Input = String

// MARK: Properties

/// The set of allowed characters.
public let characterSet: CharacterSet

/// The validation error.
/// The validation error returned if input contains invalid characters.
public let error: IValidationError

// MARK: Initialization

/// Initializes a characters validation rule.
///
/// - Parameters:
/// - characterSet: Allowed character set.
/// - error: The validation error to return if input fails validation.
public init(characterSet: CharacterSet, error: IValidationError) {
self.characterSet = characterSet
self.error = error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,50 @@
// Copyright © 2025 Space Code. All rights reserved.
//

/// A credit card validation rule.
/// Validates credit card numbers using card type prefixes and the Luhn algorithm.
/// Supports Visa, MasterCard, Amex, JCB, and UnionPay.
/// The validation checks both the prefix/length of the card and the Luhn checksum.
///
/// # Example:
/// ```swift
/// let rule = CreditCardValidationRule(types: [.visa, .masterCard], error: "Invalid card")
/// rule.validate(input: "4111 1111 1111 1111") // true for Visa
/// ```
public struct CreditCardValidationRule: IValidationRule {
// MARK: Types

/// Represents the supported credit card types.
/// Each type has specific prefix and length rules.
public enum CardType: String, Sendable, CaseIterable {
case visa, masterCard, amex, jcb, unionPay
/// Visa cards start with 4 and have 13, 16, or 19 digits.
case visa
/// MasterCard cards start with 51–55 and have 16 digits.
case masterCard
/// American Express cards start with 34 or 37 and have 15 digits.
case amex
/// JCB cards start with 3528–3589 and have 16 digits.
case jcb
/// UnionPay cards start with 62 and have 16–19 digits.
case unionPay
}

public typealias Input = String

// MARK: Properties

/// Allowed card types for validation.
public let types: [CardType]

/// The validation error.
/// Validation error returned if the card is invalid.
public let error: IValidationError

// MARK: Initialization

/// Initializes a credit card validation rule.
///
/// - Parameters:
/// - types: The allowed card types. Defaults to all supported card types.
/// - error: The validation error to return if input fails validation.
public init(types: [CardType] = CardType.allCases, error: IValidationError) {
self.types = types
self.error = error
Expand Down
15 changes: 13 additions & 2 deletions Sources/ValidatorCore/Classes/Rules/EmailValidationRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,30 @@

import Foundation

/// An email validation rule.
/// Validates that a string is a valid email address.
/// Uses a regular expression conforming to RFC 5322 (simplified).
///
/// # Example:
/// ```swift
/// let rule = EmailValidationRule(error: "Invalid email")
/// rule.validate(input: "user@example.com") // true
/// ```
public struct EmailValidationRule: IValidationRule {
// MARK: Types

public typealias Input = String

// MARK: Properties

/// The validation error.
/// Validation error returned if the email is invalid.
public let error: IValidationError

// MARK: Initialization

/// Initializes an email validation rule.
///
/// - Parameters:
/// - error: The validation error to return if input fails validation.
public init(error: IValidationError) {
self.error = error
}
Expand Down
24 changes: 20 additions & 4 deletions Sources/ValidatorCore/Classes/Rules/LengthValidationRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,41 @@

import Foundation

/// A length validation rule.
/// Validates that a string length is within the specified range.
///
/// This rule checks whether the input string's length is greater than or equal to `min`
/// and less than or equal to `max`. If the string length falls outside this range, validation fails.
///
/// # Example:
/// ```swift
/// let rule = LengthValidationRule(min: 3, max: 10, error: "Length out of range")
/// rule.validate(input: "Hello") // true
/// rule.validate(input: "Hi") // false
/// ```
public struct LengthValidationRule: IValidationRule {
// MARK: Types

public typealias Input = String

// MARK: Properties

/// The minimum length.
/// The minimum allowed length of the string. Defaults to `0`.
public let min: Int

/// The maximum length.
/// The maximum allowed length of the string. Defaults to `Int.max`.
public let max: Int

/// The validation error.
/// The validation error returned if the input does not satisfy the length constraints.
public let error: IValidationError

// MARK: Initialization

/// Initializes a length validation rule.
///
/// - Parameters:
/// - min: The minimum allowed string length. Defaults to 0.
/// - max: The maximum allowed string length. Defaults to `Int.max`.
/// - error: The validation error returned if input is invalid.
public init(min: Int = .zero, max: Int = .max, error: IValidationError) {
self.min = min
self.max = max
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@

import Foundation

/// A non empty validation rule.
/// Validates that a string is not empty.
///
/// # Example:
/// ```swift
/// let rule = NonEmptyValidationRule(error: "Field required")
/// rule.validate(input: "") // false
/// rule.validate(input: "Text") // true
/// ```
public struct NonEmptyValidationRule: IValidationRule {
// MARK: Types

Expand Down
Loading