Files
scan-receipts/app/ios/ScanReceipts/Networking/APIError.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))."
}
}
}