Skip to content

Server Errors

swift
import SwiftUI
import FormCraft

private struct ServerError: LocalizedError {
    let code: Int
    let errors: [String: [String]]

    var errorDescription: String? { "Server error \(code)" }
}

private struct PlayerRepository {
    struct PlayerDTO {
        let firstName: String
        let lastName: String
    }

    func fetchPlayer() async -> PlayerDTO {
        try? await Task.sleep(nanoseconds: 3_000_000_000)
        return .init(firstName: "First name", lastName: "Last name")
    }

    func updatePlayer(player: PlayerDTO) async -> Result<PlayerDTO, ServerError> {
        try? await Task.sleep(nanoseconds: 3_000_000_000)

        if player.firstName.count > 4 {
            return .failure(
                .init(
                    code: 400,
                    errors: ["firstName": ["First name must be 4 characters or fewer."]]
                )
            )
        }

        return .success(player)
    }
}

private struct FormFields: FormCraftFields {
    var firstName = FormCraftField(name: "firstName", value: "") { value in
        await FormCraftValidationRules()
            .string()
            .notEmpty()
            .validate(value: value)
    }

    var lastName = FormCraftField(name: "lastName", value: "") { value in
        await FormCraftValidationRules()
            .string()
            .notEmpty()
            .validate(value: value)
    }
}

struct ServerErrorsFormView: View {
    @StateObject private var form = FormCraft(fields: FormFields())
    private let playerService = PlayerRepository()

    private func handleSubmit(fields: FormCraftValidatedFields<FormFields>) async {
        let response = await playerService.updatePlayer(player: .init(
            firstName: fields.firstName,
            lastName: fields.lastName
        ))

        switch response {
        case .success:
            print("Profile updated.")
        case .failure(let serverError):
            form.setErrors(errors: serverError.errors)
        }
    }

    func onTask() async {
        let data = await playerService.fetchPlayer()
        form.setValues(values: [
            \.firstName: data.firstName,
            \.lastName: data.lastName
        ])
    }

    var body: some View {
        VStack {
            FormCraftView(formConfig: form) {
                FormCraftControllerView(
                    formConfig: form,
                    key: \.firstName
                ) { value, field in
                    TextField("First name", text: value)
                        .textFieldStyle(.roundedBorder)
                    Text(field.errors.first ?? "")
                        .foregroundStyle(.red)
                }

                FormCraftControllerView(
                    formConfig: form,
                    key: \.lastName
                ) { value, field in
                    TextField("Last name", text: value)
                        .textFieldStyle(.roundedBorder)
                    Text(field.errors.first ?? "")
                        .foregroundStyle(.red)
                }
            }

            Button("Save", action: form.handleSubmit(onSuccess: handleSubmit))
                .disabled(form.formState.isSubmitting)
        }
        .task { await onTask() }
    }
}

Released under the MIT License.