Foundation for turning the app's try?-swallowed API failures (see the pins bug) into something self-diagnosing instead of silent, without a manual "Retry" button nagging the user for every blip. RetryPolicy.withRetry wraps a call with exponential backoff, but only for OutlineAPIError.transport - a decode/auth/server error will look identical on a second try, so those fail immediately instead of burning the cooldown window. CachingOutlineAPIClient now runs every live call (both the cached-read path and the queueable-write path) through it, and keeps a per-category sliding-window failure log: repeatedFailureSummaries() surfaces a category only once it's failed 3+ times in 5 minutes with a structural (non-transport) error - plain connectivity loss is deliberately excluded since that already has its own offline UI elsewhere, and logging it here too would just be a redundant second banner every time Wi-Fi drops. Pull-based (polled), not push - this actor has no UI dependency of its own, so the Outpost-side banner reads this periodically instead of the client taking a callback. Categories are coarse (documents, collections, pins, subscriptions, stars, drafts, etc.) and the summaries carry no document content or server URL, only a generic error description - safe to show a user or attach to a bug report as-is. 10 new tests (RetryPolicyTests + CachingOutlineAPIClientTests), 92/92 passing overall.
20 lines
684 B
Swift
20 lines
684 B
Swift
import Foundation
|
|
|
|
/// A category of API call that's failed repeatedly within a short window —
|
|
/// see `CachingOutlineAPIClient.repeatedFailureSummaries()`. Deliberately
|
|
/// carries no request/response payload, document content, or server URL:
|
|
/// this is meant to be safe to show a user or attach to a bug report as-is.
|
|
public struct RepeatedFailure: Sendable, Identifiable, Equatable {
|
|
public let category: String
|
|
public let message: String
|
|
public let count: Int
|
|
|
|
public var id: String { category }
|
|
|
|
public init(category: String, message: String, count: Int) {
|
|
self.category = category
|
|
self.message = message
|
|
self.count = count
|
|
}
|
|
}
|