Files
makata/Sources/form/protocol/protocol-formable.swift
2023-02-03 15:56:38 +08:00

98 lines
2.1 KiB
Swift

//
// File.swift
//
//
// Created by Michael Ong on 2/3/23.
//
import Foundation
public protocol Formable: AnyObject {
associatedtype FormData
var formHandler: FormHandler<FormData> { get }
func submitData(form: FormData) async throws
}
public extension Formable {
func submit() async throws {
try await formHandler.submit { [unowned self] shape in
try await submitData(form: shape)
}
}
}
@dynamicMemberLookup
public class FormHandler<Shape> {
public enum Errors: Error {
case invalid(FormValidation<Shape>.Result)
}
public struct State {
public let isValid: Bool
public let validationResult: FormValidation<Shape>.Result
}
public typealias UpdatesHandler = (Shape, State) async -> Void
public var current: Shape
var updateHandler: UpdatesHandler = { _, _ async in }
var validations: FormValidation<Shape>?
public init(initial: Shape) {
self.current = initial
}
func submit(_ callback: @escaping (Shape) async throws -> Void) async throws {
let errors = validations?.validate(current) ?? .noErrors
if !errors.fields.isEmpty {
throw Errors.invalid(errors)
}
try await callback(current)
}
func pushUpdates() {
Task {
let result = validations?.validate(current) ?? .noErrors
await updateHandler(current, .init(isValid: result.fields.isEmpty, validationResult: result))
}
}
}
extension FormHandler {
@discardableResult
public func callAsFunction(_ callback: @escaping UpdatesHandler) -> Self {
updateHandler = callback
pushUpdates()
return self
}
@discardableResult
public func setValidationHandler(_ handler: FormValidation<Shape>?) -> Self {
validations = handler
pushUpdates()
return self
}
}
extension FormHandler {
public subscript<Value>(dynamicMember member: WritableKeyPath<Shape, Value>) -> Value {
get {
current[keyPath: member]
}
set {
current[keyPath: member] = newValue
pushUpdates()
}
}
}