import Foundation
/// Every failure the client throws, tagged by kind. Behaviour matches the sibling
/// TypeScript client: a non-2xx answer is `.api`, a network or decoding failure is
/// `.transport`, a bad call is `.config`, and only a poll deadline is `.timeout`.
public enum IrdError: Error, Sendable, Equatable, CustomStringConvertible {
/// The API answered with a non-2xx status. `code` is the API's error code when the
/// body carried one; `message` is its message, or the raw text for non-JSON bodies.
case api(status: Int, code: String?, message: String, body: JSONValue?)
/// DNS, TLS, socket, timeout, or an unparseable 2xx body.
case transport(String)
/// A mistake detected before any request was sent.
case config(String)
/// The client-side domain gate refused a mutation.
case domainDenied(domain: String?, pattern: String?, op: String)
/// A poll loop passed its deadline; `last` is the last response seen.
case timeout(message: String, last: JSONValue?)
/// `jobs.run`: the job reached the `failed` state.
case jobFailed(Job)
/// `ipfs.addFile`: the upload ended `failed` or `expired`.
case uploadFailed(Upload)
public var status: Int? { if case .api(let s, _, _, _) = self { return s }; return nil }
public var code: String? { if case .api(_, let c, _, _) = self { return c }; return nil }
public var isAuth: Bool { status == 401 }
public var isInsufficientCredits: Bool { status == 402 }
public var isForbidden: Bool { status == 403 }
public var isNotFound: Bool { status == 404 }
public var isRateLimited: Bool { status == 429 }
/// A sensitive-domain step-up: the call must be repeated with a fresh `totp_code`.
public var needsTOTP: Bool { code == "totp-required" || code == "invalid-totp" }
/// Sensitive domains refuse API keys; sign in with an account session instead.
public var isAPIKeyBlocked: Bool { code == "sensitive-domain-api-key-blocked" }
public var description: String {
switch self {
case .api(let status, let code, let message, _):
return code.map { "\(message) (\($0), HTTP \(status))" } ?? "\(message) (HTTP \(status))"
case .transport(let detail): return "Transport error: \(detail)"
case .config(let detail): return "Configuration error: \(detail)"
case .domainDenied(let domain, _, let op):
return "Domain gate: \(domain ?? "(unknown domain)") may not be touched by \(op)"
case .timeout(let message, _): return message
case .jobFailed(let job): return "Job \(job.id) failed: \(job.error ?? "no error reported")"
case .uploadFailed(let upload): return "Upload \(upload.id) \(upload.status.rawValue): \(upload.error ?? "no error reported")"
}
}
}