import Foundation /// A decoded `{ "error": "some_code" }` body from the backend. Every route /// under `src/app/api/**` returns one of these machine-readable codes on /// failure (see `src/lib/auth/errors.ts` for the auth ones) — the app is /// responsible for turning the code into user-facing German copy, the same /// way the web dashboard's client code does. struct APIErrorBody: Decodable { let error: String let retryAfter: Int? } enum APIError: Error, LocalizedError { /// HTTP 401 — no/invalid/expired session. Callers should route back to /// the login screen and clear the stored token. case unauthorized /// A recognised `{ error: }` body, with the HTTP status attached /// (403 pro_required, 402 scan_limit_reached, 429 rate_limited, ...). case server(code: String, status: Int, retryAfterSeconds: Int?) /// A non-2xx response with a body that isn't the `{ error }` shape. case unexpectedStatus(Int) case network(Error) case decoding(Error) var errorDescription: String? { switch self { case .unauthorized: return "Bitte melde dich erneut an." case .server(let code, _, let retryAfter): return APIError.message(forCode: code, retryAfterSeconds: retryAfter) case .unexpectedStatus(let status): return "Unerwartete Antwort vom Server (\(status))." case .network: return "Keine Verbindung zum Server. Prüfe deine Internetverbindung." case .decoding: return "Antwort des Servers konnte nicht gelesen werden." } } /// Central place to translate a backend error code into German UI copy. /// Extend this switch as features land — it deliberately mirrors what the /// web app's route handlers can return, not a guess at future codes. static func message(forCode code: String, retryAfterSeconds: Int?) -> String { switch code { case "invalid_credentials": return "E-Mail oder Passwort ist falsch." case "email_not_verified": return "Bitte bestätige zuerst deine E-Mail-Adresse." case "rate_limited": if let seconds = retryAfterSeconds, seconds > 0 { return "Zu viele Versuche. Bitte in \(seconds) Sekunden erneut versuchen." } return "Zu viele Versuche. Bitte später erneut versuchen." case "invalid_email": return "Diese E-Mail-Adresse ist ungültig." case "weak_password": return "Das Passwort ist zu schwach." case "disposable_email": return "Bitte verwende eine reguläre E-Mail-Adresse." case "mail_failed": return "Bestätigungs-E-Mail konnte nicht gesendet werden. Bitte später erneut versuchen." case "database_unavailable": return "Server vorübergehend nicht erreichbar. Bitte später erneut versuchen." case "pro_required": return "Diese Funktion ist Pro-Nutzern vorbehalten." case "scan_limit_reached": return "Dein monatliches Scan-Kontingent ist aufgebraucht." case "daily_scan_limit_reached": return "Tageslimit für Scans erreicht. Bitte morgen erneut versuchen." case "csrf_failed": return "Sicherheitsprüfung fehlgeschlagen. Bitte App neu starten." case "unauthorized": return "Bitte melde dich erneut an." default: return "Etwas ist schiefgelaufen (\(code))." } } }