79 lines
3.5 KiB
Swift
79 lines
3.5 KiB
Swift
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: <code> }` 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))."
|
|
}
|
|
}
|
|
}
|