diff --git a/docs/development/current/main/phase110_filehandlebox_design.md b/docs/development/current/main/phase110_filehandlebox_design.md new file mode 100644 index 00000000..61eb03b1 --- /dev/null +++ b/docs/development/current/main/phase110_filehandlebox_design.md @@ -0,0 +1,583 @@ +# Phase 110: FileHandleBox 設計(複数回アクセス対応) + +## 0. ゴール + +- Phase 108 で実装した FileBox(「1ショット I/O」:read/write 1回ずつ)を補完する +- FileHandleBox(「複数回アクセス I/O」:open → read/write/read → close)を設計・実装 +- **Ring0FsFileIo を内部で再利用**して、レイヤー統一を維持 +- **RuntimeProfile との整合**:Default では使用可能、NoFs では使用不可 + +--- + +## 1. スコープと非スコープ + +### スコープ(今回やること) + +1. **設計ドキュメントの作成** + - FileBox / FileHandleBox / FileIo / FsApi の役割と関係を定義 + - FileHandleBox の API(open/read/write/close 等)とライフサイクルを決める + - 二重 open 時の仕様を明記 + +2. **Rust 側の最小実装** + - FileHandleBox の型スケルトンと trait 実装(NyashBox)を追加 + - Ring0FsFileIo を内部で使い回す形で、最小の open/read/write/close を通す + - close() 後の挙動を明確に定義 + +3. **プロファイルとの整合** + - RuntimeProfile::Default のときは FileHandleBox が使用可能 + - RuntimeProfile::NoFs のときは FileHandleBox は禁止(open 時にエラー) + +### 非スコープ(今回はやらない) + +- FileHandleBox を Nyash (.hako) 側からフル運用するサンプル大量追加(必要なら 1–2 個の最小例だけ) +- ファイルロック/権限/並行書き込みなどの高度機能 +- FileBox API 自体の破壊的変更(既存の read/write の意味は維持) +- append mode(Phase 111 で扱う) + +--- + +## 2. Task 1: 設計ドキュメント作成 + +### 2.1 ファイル + +- `docs/development/current/main/phase110_filehandlebox_design.md`(このドキュメント) + +### 2.2 目的と役割分担 + +**FileBox(Phase 108)**: +- シンプルな 1 ショット I/O +- `read(path) -> String` / `write(path, content) -> OK/Error` のようなメソッド +- ファイルを開いて → 読む/書く → 閉じるをすべて隠す +- ユースケース:ログ書き込み、ワンショット設定ファイル読み込み + +**FileHandleBox(Phase 110)**: +- 複数回アクセス I/O +- ハンドルで 1 ファイルを確保 → open/read/write/close を自分で制御 +- ユースケース:大きなテキストファイルの行単位読み込み、逐次更新 + +**レイヤー図**: +``` +[FileBox] [FileHandleBox] + | | + └──────┬────────────────┘ + | (FileIo trait) + v + [Ring0FsFileIo] + | + v (FsApi) + [Ring0.fs] + | + v + [std::fs] +``` + +### 2.3 ライフサイクル設計 + +#### FileHandleBox のライフサイクル + +``` +1. birth() / new() + └─ path 未指定、io 未初期化(None) + └ファイルはまだ開かれない + +2. open(path, mode) + └─ Ring0FsFileIo をこのインスタンス用に作成して保持 + └─ FileIo.open(path) を内部で呼び出す + └─ 以後 read/write が利用可能に + +3. 複数回 read/write + └─ 同じハンドルで繰り返し呼び出し可能 + └─ close() されるまで有効 + +4. close() + └─ 内部 FileIo をリセット(Option::None に) + └─ 以後 read/write は Err("FileHandleBox is not open") + +5. Drop 時 + └─ close() 忘れがあれば、警告ログを出す(Phase 110 では実装可能、または後回し) +``` + +#### .hako 側での使用パターン + +```nyash +// パターン1: 新しいファイルに書き込み +local h = new FileHandleBox() +h.open("/tmp/output.txt", "w") +h.write("line 1") +h.write("line 2") +h.close() + +// パターン2: 既存ファイルを読む +local h = new FileHandleBox() +h.open("/tmp/input.txt", "r") +local content = h.read() +h.close() + +// パターン3: close() 忘れ(警告ログまたはパニック、Phase 110 で決定) +local h = new FileHandleBox() +h.open("/tmp/data.txt", "w") +h.write("data") +// close() なしで終了 ← 警告が出るかもしれない +``` + +### 2.4 二重 open の仕様(重要) + +**方針**: +- 最初の open → 成功、io が Some(FileIo) に +- 2 番目の open → エラーを返す(Fail-Fast) +- 理由:複数ファイルハンドルが必要なら複数 FileHandleBox インスタンスを使う + +**実装**: +```rust +pub fn open(&mut self, path: &str, mode: &str) -> Result<(), String> { + // 既に open 済みなら error + if self.io.is_some() { + return Err("FileHandleBox is already open. Call close() first.".to_string()); + } + // 新規 open + let io = Arc::new(Ring0FsFileIo::new(self.ring0.clone())); + io.open(path)?; + self.io = Some(io); + Ok(()) +} +``` + +### 2.5 close() 後の読み書き + +```rust +pub fn read_to_string(&self) -> Result { + self.io.as_ref() + .ok_or("FileHandleBox is not open".to_string())? + .read() +} + +pub fn write_all(&self, content: &str) -> Result<(), String> { + self.io.as_ref() + .ok_or("FileHandleBox is not open".to_string())? + .write(content) +} +``` + +--- + +## 3. Task 2: API 定義(Rust 側インターフェース) + +### 3.1 ファイル + +- `src/boxes/file/mod.rs`(または新規 `src/boxes/file/handle_box.rs`) +- `src/boxes/file/provider.rs`(必要に応じて) + +### 3.2 Rust 側 struct 定義 + +```rust +/// Phase 110: FileHandleBox +/// +/// ハンドルベースのファイル I/O。 +/// open(path, mode) → read/write → close() という複数回アクセスをサポート。 +pub struct FileHandleBox { + base: BoxBase, + path: String, + mode: String, + io: Option>, // 各インスタンスが独立した FileIo を保持 +} +``` + +### 3.3 NyashBox 実装 + +```rust +impl NyashBox for FileHandleBox { + fn type_name(&self) -> &str { + "FileHandleBox" + } + + fn to_string_box(&self) -> StringBox { + StringBox::new(format!("FileHandleBox(path={}, mode={}, open={})", + self.path, self.mode, self.is_open())) + } + + fn equals(&self, _other: &dyn NyashBox) -> bool { + // FileHandleBox インスタンスは path + mode で比較 + // 厳密でなくて OK + false // 簡易実装 + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} +``` + +### 3.4 メソッドセット + +```rust +impl FileHandleBox { + /// 新規 FileHandleBox を作成(ファイルはまだ open されない) + pub fn new() -> Self { + FileHandleBox { + base: BoxBase::new(), + path: String::new(), + mode: String::new(), + io: None, + } + } + + /// ファイルを開く + /// + /// # Arguments + /// - path: ファイルパス + /// - mode: "r" (読み込み) or "w" (上書き) ※ Phase 111 で "a" (append) 追加予定 + /// + /// # Error + /// - 既に open されている場合: "FileHandleBox is already open. Call close() first." + /// - ファイルが見つからない場合(mode="r"): "File not found" + pub fn open(&mut self, path: &str, mode: &str) -> Result<(), String> { + // 二重 open チェック + if self.io.is_some() { + return Err("FileHandleBox is already open. Call close() first.".to_string()); + } + + // mode 検証 + if mode != "r" && mode != "w" { + return Err(format!("Unsupported mode: {}. Use 'r' or 'w'", mode)); + } + + // 新規 FileIo 作成(Ring0FsFileIo) + // ※ provider_lock から取得するか、直接生成するかは実装で決定 + let io = Arc::new(Ring0FsFileIo::new(ring0.clone())); + io.open(path)?; + + self.path = path.to_string(); + self.mode = mode.to_string(); + self.io = Some(io); + Ok(()) + } + + /// ファイルの内容を全て読む + /// + /// # Error + /// - open されていない場合: "FileHandleBox is not open" + pub fn read_to_string(&self) -> Result { + self.io.as_ref() + .ok_or("FileHandleBox is not open".to_string())? + .read() + } + + /// ファイルに内容を書き込む(上書きモード) + /// + /// # Error + /// - open されていない場合: "FileHandleBox is not open" + /// - write mode でない場合: "FileHandleBox opened in read mode" + pub fn write_all(&self, content: &str) -> Result<(), String> { + if self.mode != "w" { + return Err("FileHandleBox opened in read mode".to_string()); + } + + self.io.as_ref() + .ok_or("FileHandleBox is not open".to_string())? + .write(content) + } + + /// ファイルを閉じる + /// + /// # Error + /// - open されていない場合: "FileHandleBox is not open" + pub fn close(&mut self) -> Result<(), String> { + if self.io.is_none() { + return Err("FileHandleBox is not open".to_string()); + } + + // 内部 FileIo を drop + self.io.take(); + self.path.clear(); + self.mode.clear(); + Ok(()) + } + + /// ファイルが open されているかチェック + pub fn is_open(&self) -> bool { + self.io.is_some() + } +} +``` + +### 3.5 Ring0FsFileIo の独立性(重要) + +**設計原則**: +- 各 FileHandleBox インスタンスが **独立した FileIo を保持** +- 複数の FileHandleBox が同時に異なるファイルを open できる + +**例**: +```rust +let mut h1 = FileHandleBox::new(); +h1.open("/tmp/file1.txt", "r")?; + +let mut h2 = FileHandleBox::new(); +h2.open("/tmp/file2.txt", "w")?; + +// h1 と h2 は別々の FileIo(Ring0FsFileIo)を持つ +// h1.read() と h2.write() は同時実行可能 + +h1.close()?; +h2.close()?; +``` + +--- + +## 4. Task 3: プロファイルとの整合 + +### 4.1 ファイル + +- `src/runtime/provider_lock.rs`(必要に応じて) +- `src/runtime/runtime_profile.rs` +- `docs/development/current/main/phase110_filehandlebox_design.md`(このドキュメント) + +### 4.2 FileHandleBox のプロファイル位置づけ + +**RuntimeProfile::Default**: +- **位置づけ**: optional(コアボックスではない) +- **動作**: + - provider_lock に FileIo が登録されている + - FileHandleBox.open() は成功する + - read/write 可能 + +**RuntimeProfile::NoFs**: +- **位置づけ**: 使用不可(disabled) +- **動作**: + - provider_lock に FileIo が登録されない(NoFsFileIo のみ) + - FileHandleBox.open() を呼ぶと: + ``` + Err("File I/O disabled in no-fs profile. FileHandleBox is not available.") + ``` + +**将来計画(Phase 113+)**: +- **RuntimeProfile::TestMock**: mock FileIo を使用(テスト用) +- **RuntimeProfile::Sandbox**: 指定ディレクトリのみアクセス可能 +- **RuntimeProfile::ReadOnly**: 読み取り専用(write Err) + +### 4.3 Policy 記述 + +```markdown +## プロファイル別の可用性 + +| Profile | FileBox | FileHandleBox | 特性 | +|----------|---------|---------------|------| +| Default | ✅ | ✅ | 完全なファイル I/O | +| NoFs | ❌ | ❌ | ファイル I/O 禁止 | +| TestMock (TBD) | ✅ mock | ✅ mock | テスト用ダミー | +| Sandbox (TBD) | ✅ dir限定 | ✅ dir限定 | サンドボックス | + +**注意**: FileHandleBox は optional boxes だが、NoFs では +「disabled」として扱う(optional ではなく「使用不可」) +``` + +--- + +## 5. Task 4: 最小限のテストとサンプル + +### 5.1 ファイル + +- `src/boxes/file/tests.rs`(新規 or 既存に追加) +- `apps/examples/file_handle_min.hako`(オプション、.hako 側のサンプル) + +### 5.2 テスト内容 + +#### テスト 1: Default プロファイル - 基本動作 + +```rust +#[test] +fn test_filehandlebox_basic_write_read() { + // 新しいファイルに書き込み + let mut h = FileHandleBox::new(); + assert!(!h.is_open()); + + let tmp_path = "/tmp/phase110_test_write_read.txt"; + h.open(tmp_path, "w").expect("open failed"); + assert!(h.is_open()); + + h.write_all("hello world").expect("write failed"); + h.close().expect("close failed"); + assert!(!h.is_open()); + + // 同じファイルを読む + let mut h2 = FileHandleBox::new(); + h2.open(tmp_path, "r").expect("open failed"); + let content = h2.read_to_string().expect("read failed"); + assert_eq!(content, "hello world"); + h2.close().expect("close failed"); + + // cleanup + std::fs::remove_file(tmp_path).ok(); +} +``` + +#### テスト 2: 二重 open error + +```rust +#[test] +fn test_filehandlebox_double_open_error() { + let mut h = FileHandleBox::new(); + h.open("/tmp/test.txt", "w").expect("first open"); + + // 2 番目の open は error + let result = h.open("/tmp/test2.txt", "w"); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("already open")); +} +``` + +#### テスト 3: close() 後のアクセス error + +```rust +#[test] +fn test_filehandlebox_closed_access_error() { + let mut h = FileHandleBox::new(); + h.open("/tmp/test.txt", "w").expect("open"); + h.close().expect("close"); + + // close 後の read は error + let result = h.read_to_string(); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("not open")); +} +``` + +#### テスト 4: NoFs プロファイル - open error + +```rust +#[test] +fn test_filehandlebox_nofs_profile_disabled() { + // NYASH_RUNTIME_PROFILE=no-fs で実行される想定 + let profile = RuntimeProfile::NoFs; + + let mut h = FileHandleBox::new(); + let result = h.open_with_profile("/tmp/test.txt", "r", &profile); + + assert!(result.is_err()); + assert!(result.unwrap_err().contains("disabled in no-fs profile")); +} +``` + +### 5.3 .hako 側サンプル(オプション) + +**apps/examples/file_handle_min.hako**: +```nyash +// File handle を使った行単位読み込みのプロトタイプ(将来) +// Phase 110 では不要。Phase 111 以降で追加予定 +``` + +--- + +## 6. Task 5: ドキュメント更新 + +### 6.1 ファイル + +- `docs/development/current/main/core_boxes_design.md`(修正) +- `docs/development/current/main/ring0-inventory.md`(修正) +- `CURRENT_TASK.md`(修正) + +### 6.2 やること + +#### core_boxes_design.md 更新 + +新セクション「5.5 Phase 110 - FileHandleBox」を追加: + +```markdown +### 5.5 Phase 110 - FileHandleBox + +FileBox(ワンショット I/O)を補完するハンドルベースのファイル I/O。 + +- **位置づけ**: core_optional(future で core_required に昇格の可能性) +- **API**: open(path, mode) → read/write → close() +- **プロファイル対応**: Default ✅、NoFs ❌ +- **実装**: Ring0FsFileIo を内部で再利用 + +詳細: [Phase 110 設計書](phase110_filehandlebox_design.md) +``` + +#### ring0-inventory.md 更新 + +「File I/O Service」セクションの「Future Expansion」に追記: + +```markdown +## Future Expansion + +- Phase 110: FileHandleBox(ハンドルベース複数回アクセス) +- Phase 111: append mode サポート +- Phase 112: metadata / stat サポート +- Phase 113: Ring0 service registry 統一化 +``` + +#### CURRENT_TASK.md 更新 + +- Phase 110 を「計画中 → 進行中」に変更(実装開始時) +- Phase 110 完了後に「進行中 → 完了」に更新 +- Backlog の「FileHandleBox」項目を消す +- 次の候補(append mode, metadata)を記載 + +--- + +## 7. 実装チェックリスト(Phase 110) + +- [ ] phase110_filehandlebox_design.md が存在し、全 5 Task が記述されている +- [ ] Rust 側に FileHandleBox struct が追加されている +- [ ] FileHandleBox が NyashBox を実装している +- [ ] open/read/write/close/is_open メソッドが全て実装されている +- [ ] 二重 open が Err を返すことが確認されている +- [ ] close() 後のアクセスが Err を返すことが確認されている +- [ ] Default プロファイルでの基本動作テストが PASS +- [ ] NoFs プロファイルでの open Err テストが PASS +- [ ] FileBox の既存 API と挙動は変わっていない +- [ ] core_boxes_design.md / ring0-inventory.md / CURRENT_TASK.md が Phase 110 と整合している +- [ ] cargo build --release SUCCESS +- [ ] cargo test --release 全テスト PASS + +--- + +## 8. 設計原則(Phase 110 で確立) + +### 複数ファイルアクセス パターン + +``` +【1つのファイルを複数回】 + local h = new FileHandleBox() + h.open("/file", "w") + h.write("data1") + h.write("data2") + h.close() + +【複数のファイル同時アクセス】 + local h1 = new FileHandleBox() + h1.open("/file1", "r") + + local h2 = new FileHandleBox() + h2.open("/file2", "w") + + h1.close() + h2.close() +``` + +### Fail-Fast 原則の適用 + +- open() 呼び出し時に既に open 済み → 即座に Err +- close() 後の read/write → 即座に Err +- NoFs profile で open → 即座に Err + +### 後方互換性 + +- FileBox は完全に独立(既存 API 変更なし) +- FileHandleBox は新規 Box として独立 +- Ring0FsFileIo の変更なし + +--- + +## 9. 将来への拡張ポイント + +- **Phase 111**: append mode (`mode = "a"`) +- **Phase 112**: file metadata / stat(size, mtime 等) +- **Phase 113**: Ring0 service registry 統一化 +- **Phase 114**: 並行アクセス安全性(Arc>) +- **Phase 115**: file encoding explicit 指定(UTF-8 以外) + +--- + +**Phase 110 設計書作成日**: 2025-12-03(修正版 5点統合)