use crate::marshal::{register_typed_fn_1, register_typed_fn_2};
use crate::module_exports::ModuleExports;
use crate::stdlib::runtime_policy::{FileSystemProvider, RealFileSystem};
use crate::typed_module_exports::{ConcreteReturn, ConcreteType, TypedReturn};
use std::path::Path;
use std::sync::Arc;
pub fn create_file_module_with_provider(fs: Arc<dyn FileSystemProvider>) -> ModuleExports {
let mut module = ModuleExports::new("std::core::file");
module.description = "High-level filesystem operations".to_string();
{
let fs = Arc::clone(&fs);
register_typed_fn_1::<_, Arc<String>>(
&mut module,
"read_text",
"Read the entire contents of a file as a UTF-8 string",
"path",
"string",
ConcreteType::Result(Box::new(ConcreteType::String)),
move |path_str, ctx| {
crate::module_exports::check_fs_permission(
ctx,
shape_abi_v1::Permission::FsRead,
path_str.as_str(),
)?;
let bytes = fs
.read(Path::new(path_str.as_str()))
.map_err(|e| format!("file.read_text() failed: {}", e))?;
let text = String::from_utf8(bytes)
.map_err(|e| format!("file.read_text() invalid UTF-8: {}", e))?;
Ok(TypedReturn::Ok(ConcreteReturn::String(text)))
},
);
}
{
let fs = Arc::clone(&fs);
register_typed_fn_2::<_, Arc<String>, Arc<String>>(
&mut module,
"write_text",
"Write a string to a file, creating or truncating it",
[("path", "string"), ("content", "string")],
ConcreteType::Result(Box::new(ConcreteType::Unit)),
move |path_str, content, ctx| {
crate::module_exports::check_fs_permission(
ctx,
shape_abi_v1::Permission::FsWrite,
path_str.as_str(),
)?;
fs.write(Path::new(path_str.as_str()), content.as_bytes())
.map_err(|e| format!("file.write_text() failed: {}", e))?;
Ok(TypedReturn::Ok(ConcreteReturn::Unit))
},
);
}
{
let fs = Arc::clone(&fs);
register_typed_fn_1::<_, Arc<String>>(
&mut module,
"read_lines",
"Read a file and return its lines as an array of strings",
"path",
"string",
ConcreteType::Result(Box::new(ConcreteType::ArrayString)),
move |path_str, ctx| {
crate::module_exports::check_fs_permission(
ctx,
shape_abi_v1::Permission::FsRead,
path_str.as_str(),
)?;
let bytes = fs
.read(Path::new(path_str.as_str()))
.map_err(|e| format!("file.read_lines() failed: {}", e))?;
let text = String::from_utf8(bytes)
.map_err(|e| format!("file.read_lines() invalid UTF-8: {}", e))?;
let lines: Vec<String> = text.lines().map(|l| l.to_string()).collect();
Ok(TypedReturn::Ok(ConcreteReturn::ArrayString(lines)))
},
);
}
{
let fs = Arc::clone(&fs);
register_typed_fn_2::<_, Arc<String>, Arc<String>>(
&mut module,
"append",
"Append a string to a file, creating it if it does not exist",
[("path", "string"), ("content", "string")],
ConcreteType::Result(Box::new(ConcreteType::Unit)),
move |path_str, content, ctx| {
crate::module_exports::check_fs_permission(
ctx,
shape_abi_v1::Permission::FsWrite,
path_str.as_str(),
)?;
fs.append(Path::new(path_str.as_str()), content.as_bytes())
.map_err(|e| format!("file.append() failed: {}", e))?;
Ok(TypedReturn::Ok(ConcreteReturn::Unit))
},
);
}
module
}
pub fn create_file_module() -> ModuleExports {
create_file_module_with_provider(Arc::new(RealFileSystem))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_module_creation() {
let module = create_file_module();
assert_eq!(module.name, "std::core::file");
assert!(module.has_export("read_text"));
assert!(module.has_export("write_text"));
assert!(module.has_export("read_lines"));
assert!(module.has_export("append"));
}
#[test]
fn test_file_schemas() {
let module = create_file_module();
let read_schema = module.get_schema("read_text").unwrap();
assert_eq!(read_schema.params.len(), 1);
assert_eq!(read_schema.return_type.as_deref(), Some("Result<string>"));
let write_schema = module.get_schema("write_text").unwrap();
assert_eq!(write_schema.params.len(), 2);
}
}