-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
43fbc45
commit 9b4d743
Showing
8 changed files
with
290 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
Sources/ValidatorUI/Classes/SUI/Managers/FormField/FormField/FormField.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// | ||
// Validator | ||
// Copyright © 2023 Space Code. All rights reserved. | ||
// | ||
|
||
import Combine | ||
import Foundation | ||
import ValidatorCore | ||
|
||
public typealias ValidationPublisher = AnyPublisher<ValidationResult, Never> | ||
|
||
// MARK: - FormField | ||
|
||
@propertyWrapper | ||
public final class FormField<Value>: IFormField { | ||
// MARK: Properties | ||
|
||
@Published | ||
/// The value to validate. | ||
private var value: Value | ||
|
||
/// The validation. | ||
private let validator: IValidator | ||
|
||
/// The validation rules. | ||
private let rules: [any IValidationRule<Value>] | ||
|
||
public var wrappedValue: Value { | ||
get { value } | ||
set { value = newValue } | ||
} | ||
|
||
// MARK: Initialization | ||
|
||
public init( | ||
wrappedValue: Value, | ||
validator: IValidator = Validator(), | ||
rules: [any IValidationRule<Value>] | ||
) { | ||
value = wrappedValue | ||
self.validator = validator | ||
self.rules = rules | ||
} | ||
|
||
// MARK: IFormField | ||
|
||
public func validate(manager: some IFormFieldManager) -> any IFormValidationContainer { | ||
let subject = CurrentValueSubject<Value, Never>(value) | ||
|
||
let publisher = $value | ||
.receive(on: RunLoop.main) | ||
.handleEvents(receiveOutput: { subject.send($0) }) | ||
.map { self.validator.validate(input: $0, rules: self.rules) } | ||
.eraseToAnyPublisher() | ||
|
||
let container = FormValidationContainter( | ||
value: subject, | ||
publisher: publisher, | ||
validator: validator, | ||
rules: rules | ||
) | ||
|
||
manager.append(validator: container) | ||
|
||
return container | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
Sources/ValidatorUI/Classes/SUI/Managers/FormField/FormField/IFormField.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// | ||
// Validator | ||
// Copyright © 2023 Space Code. All rights reserved. | ||
// | ||
|
||
import Combine | ||
import Foundation | ||
import ValidatorCore | ||
|
||
/// A type that represents a field on a form. | ||
public protocol IFormField { | ||
/// Performs field validation. | ||
/// | ||
/// - Note: Create a form validation container that keeps track of the validation. | ||
/// | ||
/// - Parameter manager: The form field manager. | ||
/// | ||
/// - Returns: A validation container. | ||
func validate(manager: some IFormFieldManager) -> any IFormValidationContainer | ||
} |
34 changes: 34 additions & 0 deletions
34
Sources/ValidatorUI/Classes/SUI/Managers/FormField/FormFieldManager/FormFieldManager.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// | ||
// Validator | ||
// Copyright © 2023 Space Code. All rights reserved. | ||
// | ||
|
||
import Combine | ||
import Foundation | ||
import ValidatorCore | ||
|
||
public final class FormFieldManager: IFormFieldManager { | ||
// MARK: Properties | ||
|
||
@Published public var isValid = true | ||
|
||
private var validators: [any IFormValidationContainer] = [] | ||
|
||
// MARK: Initialization | ||
|
||
public init() {} | ||
|
||
// MARK: IFormFieldManager | ||
|
||
public func append(validator: some IFormValidationContainer) { | ||
validators.append(validator) | ||
validate() | ||
} | ||
|
||
public func validate() { | ||
// swiftlint:disable:next contains_over_filter_is_empty | ||
isValid = validators | ||
.filter { $0.validate() != .valid } | ||
.isEmpty | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
Sources/ValidatorUI/Classes/SUI/Managers/FormField/FormFieldManager/IFormFieldManager.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// | ||
// Validator | ||
// Copyright © 2023 Space Code. All rights reserved. | ||
// | ||
|
||
import Combine | ||
import Foundation | ||
import ValidatorCore | ||
|
||
/// A type that manages the validation state of a form. | ||
public protocol IFormFieldManager: ObservableObject { | ||
/// A Boolean value that indicates whether all fields on a form are valid or not. | ||
var isValid: Bool { get } | ||
|
||
/// Appends a new validator to the manager. | ||
/// | ||
/// - Parameter validator: The validation container that encompasses required validation logic. | ||
func append(validator: some IFormValidationContainer) | ||
} |
37 changes: 37 additions & 0 deletions
37
...rUI/Classes/SUI/Managers/FormField/FormValidationContainer/FormValidationContainter.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// | ||
// Validator | ||
// Copyright © 2023 Space Code. All rights reserved. | ||
// | ||
|
||
import Combine | ||
import Foundation | ||
import ValidatorCore | ||
|
||
public struct FormValidationContainter<T>: IFormValidationContainer { | ||
// MARK: Properties | ||
|
||
public var value: FormValidatorValueSubject<T> | ||
public let publisher: ValidationPublisher | ||
public let validator: IValidator | ||
public let rules: [any IValidationRule<T>] | ||
|
||
// MARK: Initialization | ||
|
||
public init( | ||
value: FormValidatorValueSubject<T>, | ||
publisher: ValidationPublisher, | ||
validator: IValidator, | ||
rules: [any IValidationRule<T>] | ||
) { | ||
self.value = value | ||
self.publisher = publisher | ||
self.validator = validator | ||
self.rules = rules | ||
} | ||
|
||
// MARK: IFormValidationContainer | ||
|
||
public func validate() -> ValidationResult { | ||
validator.validate(input: value.value, rules: rules) | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
...rUI/Classes/SUI/Managers/FormField/FormValidationContainer/IFormValidationContainer.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// | ||
// Validator | ||
// Copyright © 2023 Space Code. All rights reserved. | ||
// | ||
|
||
import Combine | ||
import Foundation | ||
import ValidatorCore | ||
|
||
public typealias FormValidatorValueSubject<Value> = CurrentValueSubject<Value, Never> | ||
|
||
// MARK: - IFormValidationContainer | ||
|
||
/// A container for form validation logic. | ||
public protocol IFormValidationContainer<Value> { | ||
associatedtype Value | ||
|
||
/// The value subject used for form validation. | ||
var value: FormValidatorValueSubject<Value> { get } | ||
|
||
/// The publisher responsible for emitting validation events. | ||
var publisher: ValidationPublisher { get } | ||
|
||
/// The validator associated with this validation container. | ||
var validator: IValidator { get } | ||
|
||
/// An array of validation rules to apply to the form field. | ||
var rules: [any IValidationRule<Value>] { get } | ||
|
||
/// Performs form field validation. | ||
/// | ||
/// - Returns: The result of the validation process. | ||
func validate() -> ValidationResult | ||
} |
52 changes: 52 additions & 0 deletions
52
Sources/ValidatorUI/Classes/SUI/ViewModifiers/FormValidationViewModifier.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// | ||
// Validator | ||
// Copyright © 2023 Space Code. All rights reserved. | ||
// | ||
|
||
import SwiftUI | ||
import ValidatorCore | ||
|
||
public struct FormValidationViewModifier<ErrorView: View>: ViewModifier { | ||
// MARK: Properties | ||
|
||
/// The result of the validation. | ||
@State private var validationResult: ValidationResult = .valid | ||
|
||
/// A container for form validation logic. | ||
private let validationContainer: any IFormValidationContainer | ||
|
||
/// A custom parameter attribute that constructs views from closures. | ||
@ViewBuilder private let content: ([any IValidationError]) -> ErrorView | ||
|
||
// MARK: Initialization | ||
|
||
public init( | ||
validationContainer: any IFormValidationContainer, | ||
@ViewBuilder content: @escaping ([any IValidationError]) -> ErrorView | ||
) { | ||
self.validationContainer = validationContainer | ||
self.content = content | ||
} | ||
|
||
// MARK: ViewModifier | ||
|
||
public func body(content: Content) -> some View { | ||
VStack(alignment: .leading) { | ||
content | ||
validationMessageView | ||
}.onReceive(validationContainer.publisher) { result in | ||
self.validationResult = result | ||
} | ||
} | ||
|
||
// MARK: Private | ||
|
||
private var validationMessageView: some View { | ||
switch validationResult { | ||
case .valid: | ||
return EmptyView().eraseToAnyView() | ||
case let .invalid(errors): | ||
return content(errors).eraseToAnyView() | ||
} | ||
} | ||
} |