TurboError から構造化エラー型へ置き換え、Cold‑Boot リトライを強化
TurboError の単一 enum を廃止し、HotwireNativeError を中心としたエラー階層を導入したことで、エラーの種類と回復可能性が明示的に扱えるようになりました。併せて、サーバー到達確認を行う RedirectHandler に 30 秒タイムアウトを設定し、Cold‑Boot 時のリトライロジックを統一的に管理します。変更は UI から内部セッションまで一貫して適用され、既存の振る舞いを保ちつつ可観測性と拡張性が向上します。
背景
従来の TurboError は HTTP ステータスやネットワーク障害を 4 つのケースで表現し、エラーの種類や再試行可否を判別しにくい設計でした。特に Cold‑Boot 時にサーバーが一時的に到達不能だった場合のリトライ判定が散在しており、テストカバレッジの維持が困難でした。これらの課題を解消するために、エラーを「HTTP」「Web」「Load」の三層に分割し、各層で isRetryable を持たせた HotwireNativeError を新設しました。
技術的な変更
エラー型階層の導入
HotwireNativeError は .http(HTTPError), .web(WebError), .load(LoadError) の三ケースを提供し、errorDescription, statusCode, urlError, isRetryable などの共通プロパティを統一しています。HTTPError はクライアントエラー (4xx) とサーバーエラー (5xx) を別 enum に分割し、408, 429, 503, 504 のみ isRetryable = true としています。WebError は URLError をラップし、オフライン・タイムアウト・SSL エラーなどをヘルパー属性で取得できるようにしました。LoadError は Turbo.js の初期化失敗を表現し、ユーザー向けメッセージとタイトルを保持します。
Navigator と Session の型変更
NavigatorDelegate.visitableDidFailRequest のシグネチャが Error から HotwireNativeError に変更され、エラーハンドリングが統一されました。Navigator では error.isRetryable を判定してリトライハンドラを生成し、再試行が不要なケースでは nil が渡されます。Session でも同様にエラー受取メソッドが更新され、retriedVisitIdentifiers が導入されて同一訪問の二重リトライを防止しています。
RedirectHandler のタイムアウトとバリデーション緩和
RedirectHandler.resolve に timeout: TimeInterval = Hotwire.config.redirectResolutionTimeout が追加され、デフォルトで 30 秒の待機時間が設定されました。HTTP ステータスコードの検証が削除され、サーバーが到達可能かどうかだけを判定するシンプルなロジックへと変更されています。エラーメッセージは LocalizedError に準拠した文字列に統一され、呼び出し側で直接利用できるようになりました。
JS ブリッジの分岐拡張
WebViewBridge が HTTP と非 HTTP の失敗を別メッセージに分割し、statusCode を伴ってネイティブ側に渡すよう修正しました。Turbo.js 側でも turboIsReady イベントを送出し、ロード失敗時に .load(.notReady) を即座に報告できるようになりました。これにより、Cold‑Boot 時のサーバー到達チェックとページロードエラーが同一フローで処理されます。
UI と設定の対応
SceneController.visitableDidFailRequest が新しいエラー型に合わせてスイッチ文で処理し、認証エラー (401) のみ特別扱いしつつ他は汎用プレゼンテーションに委譲します。HotwireConfig に redirectResolutionTimeout が追加され、タイムアウト値をアプリ側から設定可能にしました。エラービューのプロトコル ErrorPresentableView も HotwireNativeError を受け取るよう変更し、デフォルトエラービュー DefaultErrorView が新型エラーに対応しています。
設計判断
エラー型を階層化した最大の意図は、責務分離 と 再利用性 の向上です。HTTP、ネットワーク、ロードというドメインごとに型を分割することで、各層で isRetryable 等のロジックを局所的に実装でき、Navigator や Session がエラーの詳細を知る必要がなくなります。また、既存の TurboError を削除しつつ true/false 互換性は維持せず、代わりに HotwireNativeError を必須としたのは、型安全性と将来的な拡張を見据えた破壊的変更の選択です。リトライ回数を retriedVisitIdentifiers で管理し、一回だけの Cold‑Boot リトライに限定したのは、ユーザー体験の過度な遅延回避とサーバー負荷抑制というトレードオフを明示した結果です。
まとめ
本 PR はエラー表現を構造化し、リトライ判定を統一的に扱える基盤を構築したと同時に、Cold‑Boot 時のサーバー到達確認をタイムアウト付きで安全に実装しました。結果として、コードベース全体でエラー処理が一貫化され、テスト容易性と将来の拡張余地が大幅に向上しています。