feat(phase114): FileIo trait 拡張 & メタデータ統一完成
Phase 113 で公開した .hako API は変更なく、内部実装を完全統一化。 FsApi(Ring0 stateless)と FileIo(Ring1 stateful)の設計を確立。 【実装内容】 Task 1: FileIo trait 拡張 - FileStat 構造体追加(is_file/is_dir/size) - exists/stat/canonicalize メソッド追加(FileIo trait) Task 2: Ring0FsFileIo 実装 - exists(): path を Ring0.fs で確認 - stat(): Ring0.fs.metadata() を FileStat に変換 - canonicalize(): Ring0.fs.canonicalize() を String に変換 Task 3: NoFsFileIo stub 実装 - exists() → false(全ファイルが「存在しない」扱い) - stat() → Err(Unsupported)(FS 無効情報を返す) - canonicalize() → Err(Unsupported) Task 4: FileHandleBox 内部統一 - metadata_internal() を新規追加(FileIo::stat() ベース) - is_file/is_dir/size を metadata_internal() 経由に統一 - Nyash 公開 API(ny_exists/ny_size/ny_isFile/ny_isDir)は変更なし Task 5: テスト + ドキュメント - Ring0FsFileIo: 5テスト(stat/exists/canonicalize) - NoFsFileIo: 3テスト(exist/stat/canonicalize error) - FileHandleBox: 5テスト(metadata_internal/exists/is_file/is_dir) - すべてのテスト PASS 【設計原則確立】 FsApi ↔ FileIo の責務分担: - FsApi (Ring0): Stateless(パスを毎回指定) - FileIo (Ring1): Stateful(path を内部保持) - FileHandleBox: FileIo::stat() で一元化 Profile 別動作: - Default: 全機能正常動作 - NoFs: exists=false, stat/canonicalize は Unsupported エラー 【統計】 - 修正ファイル: 9ファイル - 追加行: +432行、削除: -29行 - 新規テスト: 13個(全PASS) - ビルド: SUCCESS 【効果】 - 内部実装が完全統一(二重実装・不一貫性排除) - Phase 115+ での拡張(modified_time/permissions等)が容易に - FsApi と FileIo の設計がクリアに確立 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -224,22 +224,16 @@ impl FileHandleBox {
|
||||
self.io.is_some()
|
||||
}
|
||||
|
||||
// ===== Phase 111: Metadata methods (internal Rust API) =====
|
||||
// ===== Phase 111/114: Metadata methods (internal Rust API) =====
|
||||
|
||||
/// Internal helper: Get FsMetadata from Ring0FsFileIo
|
||||
fn metadata_internal(&self) -> Result<crate::runtime::ring0::FsMetadata, String> {
|
||||
if self.path.is_empty() {
|
||||
return Err("FileHandleBox path not set".to_string());
|
||||
}
|
||||
/// Phase 114: Internal helper using FileIo::stat()
|
||||
///
|
||||
/// Unified metadata access through FileIo trait.
|
||||
fn metadata_internal(&self) -> Result<crate::boxes::file::provider::FileStat, String> {
|
||||
let io = self.io.as_ref()
|
||||
.ok_or_else(|| "FileHandleBox is not open".to_string())?;
|
||||
|
||||
// Get Ring0FsFileIo and call metadata()
|
||||
self.io
|
||||
.as_ref()
|
||||
.ok_or_else(|| "FileHandleBox not open".to_string())?
|
||||
.as_any()
|
||||
.downcast_ref::<crate::providers::ring1::file::ring0_fs_fileio::Ring0FsFileIo>()
|
||||
.ok_or_else(|| "FileIo is not Ring0FsFileIo".to_string())?
|
||||
.metadata()
|
||||
io.stat()
|
||||
.map_err(|e| format!("Metadata failed: {}", e))
|
||||
}
|
||||
|
||||
@ -247,37 +241,33 @@ impl FileHandleBox {
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - Path not set: "FileHandleBox path not set"
|
||||
/// - Not open: "FileHandleBox is not open"
|
||||
/// - Metadata failed: "Metadata failed: ERROR"
|
||||
pub fn size(&self) -> Result<u64, String> {
|
||||
self.metadata_internal().map(|meta| meta.len)
|
||||
self.metadata_internal().map(|meta| meta.size)
|
||||
}
|
||||
|
||||
/// Check if file exists
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - Path not set: "FileHandleBox path not set"
|
||||
/// - Not open: "FileHandleBox is not open"
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Returns false if file not found (no error)
|
||||
/// Uses FileIo::exists() for direct check.
|
||||
pub fn exists(&self) -> Result<bool, String> {
|
||||
if self.path.is_empty() {
|
||||
return Err("FileHandleBox path not set".to_string());
|
||||
}
|
||||
let io = self.io.as_ref()
|
||||
.ok_or_else(|| "FileHandleBox is not open".to_string())?;
|
||||
|
||||
match self.metadata_internal() {
|
||||
Ok(_) => Ok(true),
|
||||
Err(_) => Ok(false), // not found → false
|
||||
}
|
||||
Ok(io.exists())
|
||||
}
|
||||
|
||||
/// Check if path is a file
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - Path not set: "FileHandleBox path not set"
|
||||
/// - Not open: "FileHandleBox is not open"
|
||||
/// - Metadata failed: "Metadata failed: ERROR"
|
||||
pub fn is_file(&self) -> Result<bool, String> {
|
||||
self.metadata_internal().map(|meta| meta.is_file)
|
||||
@ -287,7 +277,7 @@ impl FileHandleBox {
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - Path not set: "FileHandleBox path not set"
|
||||
/// - Not open: "FileHandleBox is not open"
|
||||
/// - Metadata failed: "Metadata failed: ERROR"
|
||||
pub fn is_dir(&self) -> Result<bool, String> {
|
||||
self.metadata_internal().map(|meta| meta.is_dir)
|
||||
@ -948,4 +938,105 @@ mod tests {
|
||||
handle.ny_open(path, "r");
|
||||
handle.ny_write("data"); // This should panic (read-only mode)
|
||||
}
|
||||
|
||||
// ===== Phase 114: metadata_internal() unification tests =====
|
||||
|
||||
#[test]
|
||||
fn test_filehandlebox_metadata_internal_default() {
|
||||
init_test_provider();
|
||||
|
||||
let path = "/tmp/phase114_metadata_internal.txt";
|
||||
let _ = fs::remove_file(path);
|
||||
|
||||
// Create test file with known content
|
||||
let mut handle = FileHandleBox::new();
|
||||
handle.open(path, "w").unwrap();
|
||||
handle.write_all("12345").unwrap(); // 5 bytes
|
||||
handle.close().unwrap();
|
||||
|
||||
// Test metadata_internal() via stat()
|
||||
handle.open(path, "r").unwrap();
|
||||
let stat = handle.metadata_internal().expect("metadata_internal should succeed");
|
||||
assert!(stat.is_file);
|
||||
assert!(!stat.is_dir);
|
||||
assert_eq!(stat.size, 5);
|
||||
|
||||
handle.close().unwrap();
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filehandlebox_metadata_internal_not_open() {
|
||||
init_test_provider();
|
||||
|
||||
let handle = FileHandleBox::new();
|
||||
let result = handle.metadata_internal();
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("not open"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filehandlebox_ny_size_uses_stat() {
|
||||
init_test_provider();
|
||||
|
||||
let path = "/tmp/phase114_ny_size_stat.txt";
|
||||
let _ = fs::remove_file(path);
|
||||
|
||||
let mut handle = FileHandleBox::new();
|
||||
handle.ny_open(path, "w");
|
||||
handle.ny_write("test123"); // 7 bytes
|
||||
handle.ny_close();
|
||||
|
||||
// Verify ny_size() uses stat() internally
|
||||
handle.ny_open(path, "r");
|
||||
let size = handle.ny_size();
|
||||
assert_eq!(size.value, 7);
|
||||
handle.ny_close();
|
||||
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filehandlebox_exists_uses_fileio() {
|
||||
init_test_provider();
|
||||
|
||||
let path = "/tmp/phase114_exists_fileio.txt";
|
||||
let _ = fs::remove_file(path);
|
||||
|
||||
let mut handle = FileHandleBox::new();
|
||||
handle.ny_open(path, "w");
|
||||
handle.ny_close();
|
||||
|
||||
// exists() should use FileIo::exists()
|
||||
handle.ny_open(path, "r");
|
||||
let exists = handle.ny_exists();
|
||||
assert!(exists.value);
|
||||
handle.ny_close();
|
||||
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filehandlebox_is_file_is_dir_via_stat() {
|
||||
init_test_provider();
|
||||
|
||||
let path = "/tmp/phase114_is_file_dir.txt";
|
||||
let _ = fs::remove_file(path);
|
||||
|
||||
let mut handle = FileHandleBox::new();
|
||||
handle.ny_open(path, "w");
|
||||
handle.ny_close();
|
||||
|
||||
// Test is_file/is_dir use metadata_internal() → stat()
|
||||
handle.ny_open(path, "r");
|
||||
|
||||
let is_file = handle.is_file().expect("is_file should succeed");
|
||||
assert!(is_file);
|
||||
|
||||
let is_dir = handle.is_dir().expect("is_dir should succeed");
|
||||
assert!(!is_dir);
|
||||
|
||||
handle.ny_close();
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,6 +73,14 @@ impl FileCaps {
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 114: File statistics
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct FileStat {
|
||||
pub is_file: bool,
|
||||
pub is_dir: bool,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
/// Unified error type (thin placeholder for now)
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum FileError {
|
||||
@ -94,7 +102,16 @@ pub trait FileIo: Send + Sync {
|
||||
|
||||
/// Phase 111: Downcast support for metadata access
|
||||
fn as_any(&self) -> &dyn std::any::Any;
|
||||
// Future: exists/stat …
|
||||
|
||||
// Phase 114: Metadata operations
|
||||
/// Check if the file exists
|
||||
fn exists(&self) -> bool;
|
||||
|
||||
/// Get file statistics (metadata)
|
||||
fn stat(&self) -> FileResult<FileStat>;
|
||||
|
||||
/// Get canonicalized absolute path
|
||||
fn canonicalize(&self) -> FileResult<String>;
|
||||
}
|
||||
|
||||
/// Normalize newlines to LF (optional helper)
|
||||
|
||||
Reference in New Issue
Block a user