TurboError から構造化エラー型へ置き換え、Cold‑Boot リトライを強化

hotwired/hotwire-native-ios

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 としています。WebErrorURLError をラップし、オフライン・タイムアウト・SSL エラーなどをヘルパー属性で取得できるようにしました。LoadError は Turbo.js の初期化失敗を表現し、ユーザー向けメッセージとタイトルを保持します。

Navigator と Session の型変更

NavigatorDelegate.visitableDidFailRequest のシグネチャが Error から HotwireNativeError に変更され、エラーハンドリングが統一されました。Navigator では error.isRetryable を判定してリトライハンドラを生成し、再試行が不要なケースでは nil が渡されます。Session でも同様にエラー受取メソッドが更新され、retriedVisitIdentifiers が導入されて同一訪問の二重リトライを防止しています。

RedirectHandler のタイムアウトとバリデーション緩和

RedirectHandler.resolvetimeout: TimeInterval = Hotwire.config.redirectResolutionTimeout が追加され、デフォルトで 30 秒の待機時間が設定されました。HTTP ステータスコードの検証が削除され、サーバーが到達可能かどうかだけを判定するシンプルなロジックへと変更されています。エラーメッセージは LocalizedError に準拠した文字列に統一され、呼び出し側で直接利用できるようになりました。

JS ブリッジの分岐拡張

WebViewBridge が HTTP と非 HTTP の失敗を別メッセージに分割し、statusCode を伴ってネイティブ側に渡すよう修正しました。Turbo.js 側でも turboIsReady イベントを送出し、ロード失敗時に .load(.notReady) を即座に報告できるようになりました。これにより、Cold‑Boot 時のサーバー到達チェックとページロードエラーが同一フローで処理されます。

UI と設定の対応

SceneController.visitableDidFailRequest が新しいエラー型に合わせてスイッチ文で処理し、認証エラー (401) のみ特別扱いしつつ他は汎用プレゼンテーションに委譲します。HotwireConfigredirectResolutionTimeout が追加され、タイムアウト値をアプリ側から設定可能にしました。エラービューのプロトコル ErrorPresentableViewHotwireNativeError を受け取るよう変更し、デフォルトエラービュー DefaultErrorView が新型エラーに対応しています。

設計判断

エラー型を階層化した最大の意図は、責務分離再利用性 の向上です。HTTP、ネットワーク、ロードというドメインごとに型を分割することで、各層で isRetryable 等のロジックを局所的に実装でき、Navigator や Session がエラーの詳細を知る必要がなくなります。また、既存の TurboError を削除しつつ true/false 互換性は維持せず、代わりに HotwireNativeError を必須としたのは、型安全性と将来的な拡張を見据えた破壊的変更の選択です。リトライ回数を retriedVisitIdentifiers で管理し、一回だけの Cold‑Boot リトライに限定したのは、ユーザー体験の過度な遅延回避とサーバー負荷抑制というトレードオフを明示した結果です。

まとめ

本 PR はエラー表現を構造化し、リトライ判定を統一的に扱える基盤を構築したと同時に、Cold‑Boot 時のサーバー到達確認をタイムアウト付きで安全に実装しました。結果として、コードベース全体でエラー処理が一貫化され、テスト容易性と将来の拡張余地が大幅に向上しています。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
826dcfac

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文、背景、技術的な変更、設計判断、まとめの全セクションが揃っており、総論→各論→結論の流れが明確です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

コードブロックは使用されていませんが、Markdown記法に誤りはなく、PRリンクも有効です。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

専門的なエンジニア向けの記述で、余計な初心者向け解説はありません。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論・各論・結論の構成を保ち、段落はトピックセンテンスで始まり、1段落1トピック・適切な長さで書かれています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内の変更説明はDiffの内容と一致しており、追加されたタイムアウトや型変更、エラー階層の導入などが正確に記述されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

HotwireNativeError、RedirectHandler、Navigator などの用語がPRで使用されたものと一致し、誤用はありません。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的な主張はDiffやPR説明に裏付けられており、論理的に正しいです。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

PR情報に基づかない数値や外部知識の記述はなく、ハルシネーションは検出されません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

30秒タイムアウトやリトライが1回に限定されている点など、数値情報はDiffと合致しています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルはPRタイトルを正確に日本語化しており、内容と一致しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

LTSやリリース日程等の外部情報は記載されていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現のズレはなく、PRの記述と合致しています。