dmsc/fs/
mod.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of DMSC.
4//! The DMSC project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18#![allow(non_snake_case)]
19
20//! # File System Module
21//! 
22//! This module provides a comprehensive file system abstraction for DMSC, offering safe and reliable
23//! file operations with support for atomic writes, directory management, and structured data formats.
24//! 
25//! ## Key Components
26//! 
27//! - **DMSCFileSystem**: Public-facing file system class
28//! - **FileSystemImpl**: Internal file system implementation
29//! 
30//! ## Design Principles
31//! 
32//! 1. **Safe Operations**: All file operations are designed to be safe and reliable
33//! 2. **Atomic Writes**: Uses atomic write operations to prevent data corruption
34//! 3. **Directory Management**: Automatically creates necessary directories
35//! 4. **Structured Data Support**: Built-in support for JSON serialization and deserialization
36//! 5. **Category-Based Organization**: Organizes files into categories (logs, cache, reports, etc.)
37//! 6. **Error Handling**: Provides comprehensive error handling for all file operations
38//! 7. **Cloneable**: Designed to be easily cloned for use across different components
39//! 
40//! ## Usage
41//! 
42//! ```rust
43//! use dmsc::prelude::*;
44//! use std::path::PathBuf;
45//! 
46//! fn example() -> DMSCResult<()> {
47//!     // Create a file system with a project root
48//!     let project_root = PathBuf::from(".");
49//!     let fs = DMSCFileSystem::new_with_root(project_root);
50//!     
51//!     // Write text to a file
52//!     fs.atomic_write_text("example.txt", "Hello, DMSC!")?;
53//!     
54//!     // Read text from a file
55//!     let content = fs.read_text("example.txt")?;
56//!     println!("File content: {}", content);
57//!     
58//!     // Write JSON to a file
59//!     let data = json!({"key": "value"});
60//!     fs.write_json("example.json", &data)?;
61//!     
62//!     // Read JSON from a file
63//!     let read_data: serde_json::Value = fs.read_json("example.json")?;
64//!     println!("JSON data: {:?}", read_data);
65//!     
66//!     // Get category directories
67//!     let logs_dir = fs.logs_dir();
68//!     println!("Logs directory: {:?}", logs_dir);
69//!     
70//!     Ok(())
71//! }
72//! ```
73
74use std::fs;
75use std::io::{Read, Write};
76use std::path::{Path, PathBuf};
77use std::fs::OpenOptions;
78use std::time::SystemTime;
79
80use crate::core::DMSCResult;
81use serde::de::DeserializeOwned;
82use serde::Serialize;
83
84/// Internal filesystem implementation.
85/// 
86/// This struct provides the internal implementation of the file system functionality, including
87/// directory management, file operations, and category-based organization.
88#[derive(Clone)]
89struct FileSystemImpl {
90    /// Project root directory
91    project_root: PathBuf,
92    /// Application data root directory
93    app_data_root: PathBuf,
94}
95
96impl FileSystemImpl {
97    /// Creates a new internal file system implementation with specified roots.
98    /// 
99    /// # Parameters
100    /// 
101    /// - `project_root`: The project root directory
102    /// - `app_data_root`: The application data root directory
103    /// 
104    /// # Returns
105    /// 
106    /// A new `FileSystemImpl` instance
107    fn new_with_roots(project_root: PathBuf, app_data_root: PathBuf) -> Self {
108        FileSystemImpl { project_root, app_data_root }
109    }
110
111    /// Creates a new internal file system implementation with a project root and default app data root.
112    /// 
113    /// The default app data root is created under the project root at `.dms`.
114    /// 
115    /// # Parameters
116    /// 
117    /// - `project_root`: The project root directory
118    /// 
119    /// # Returns
120    /// 
121    /// A new `FileSystemImpl` instance
122    fn new_with_root(project_root: PathBuf) -> Self {
123        // Default app data root under project root; can be overridden by core/config.
124        let app_data_root = project_root.join(".dms");
125        FileSystemImpl::new_with_roots(project_root, app_data_root)
126    }
127
128    /// Returns the project root directory.
129    /// 
130    /// # Returns
131    /// 
132    /// A reference to the project root path
133    fn project_root(&self) -> &Path {
134        &self.project_root
135    }
136
137    /// Safely creates a directory and all its parent directories if they don't exist.
138    /// 
139    /// # Parameters
140    /// 
141    /// - `path`: The path to the directory to create
142    /// 
143    /// # Returns
144    /// 
145    /// A `DMSCResult<PathBuf>` containing the created directory path
146    fn safe_mkdir(&self, path: &Path) -> DMSCResult<PathBuf> {
147        fs::create_dir_all(path).map_err(|e| crate::core::DMSCError::Other(format!("safe_mkdir failed: {e}")))?;
148        Ok(path.to_path_buf())
149    }
150
151    /// Ensures that the parent directory of a given path exists.
152    /// 
153    /// # Parameters
154    /// 
155    /// - `path`: The path whose parent directory should be ensured
156    /// 
157    /// # Returns
158    /// 
159    /// A `DMSCResult<PathBuf>` containing the parent directory path
160    fn ensure_parent_dir(&self, path: &Path) -> DMSCResult<PathBuf> {
161        if let Some(parent) = path.parent() {
162            self.safe_mkdir(parent)
163        } else {
164            Ok(self.project_root.clone())
165        }
166    }
167
168    /// Atomically writes text to a file.
169    /// 
170    /// This method writes to a temporary file first, then renames it to the target path,
171    /// ensuring that the write operation is atomic.
172    /// 
173    /// # Parameters
174    /// 
175    /// - `path`: The path to the file to write
176    /// - `text`: The text to write to the file
177    /// 
178    /// # Returns
179    /// 
180    /// A `DMSCResult<()>` indicating success or failure
181    fn atomic_write_text(&self, path: &Path, text: &str) -> DMSCResult<()> {
182        self.ensure_parent_dir(path)?;
183        let dir = path.parent().unwrap_or_else(|| Path::new("."));
184        let ts = SystemTime::now()
185            .duration_since(SystemTime::UNIX_EPOCH)
186            .map_err(|e| crate::core::DMSCError::Other(format!("timestamp error: {e}")))?;
187        let tmp_name = format!(".tmp_{}_{}", ts.as_millis(), path.file_name().and_then(|s| s.to_str()).unwrap_or("tmp"));
188        let tmp_path = dir.join(tmp_name);
189
190        {
191            let mut file = OpenOptions::new()
192                .write(true)
193                .create(true)
194                .truncate(true)
195                .open(&tmp_path)
196                .map_err(|e| crate::core::DMSCError::Other(format!("atomic_write_text open tmp failed: {e}")))?;
197            file.write_all(text.as_bytes())
198                .map_err(|e| crate::core::DMSCError::Other(format!("atomic_write_text write failed: {e}")))?;
199            file.sync_all()
200                .map_err(|e| crate::core::DMSCError::Other(format!("atomic_write_text sync failed: {e}")))?;
201        }
202
203        fs::rename(&tmp_path, path)
204            .map_err(|e| crate::core::DMSCError::Other(format!("atomic_write_text rename failed: {e}")))?;
205
206        Ok(())
207    }
208
209    /// Atomically writes bytes to a file.
210    /// 
211    /// This method writes to a temporary file first, then renames it to the target path,
212    /// ensuring that the write operation is atomic.
213    /// 
214    /// # Parameters
215    /// 
216    /// - `path`: The path to the file to write
217    /// - `data`: The bytes to write to the file
218    /// 
219    /// # Returns
220    /// 
221    /// A `DMSCResult<()>` indicating success or failure
222    fn atomic_write_bytes(&self, path: &Path, data: &[u8]) -> DMSCResult<()> {
223        self.ensure_parent_dir(path)?;
224        let dir = path.parent().unwrap_or_else(|| Path::new("."));
225        let ts = SystemTime::now()
226            .duration_since(SystemTime::UNIX_EPOCH)
227            .map_err(|e| crate::core::DMSCError::Other(format!("timestamp error: {e}")))?;
228        let tmp_name = format!(".tmp_{}_{}", ts.as_millis(), path.file_name().and_then(|s| s.to_str()).unwrap_or("tmp"));
229        let tmp_path = dir.join(tmp_name);
230
231        {
232            let mut file = OpenOptions::new()
233                .write(true)
234                .create(true)
235                .truncate(true)
236                .open(&tmp_path)
237                .map_err(|e| crate::core::DMSCError::Other(format!("atomic_write_bytes open tmp failed: {e}")))?;
238            file.write_all(data)
239                .map_err(|e| crate::core::DMSCError::Other(format!("atomic_write_bytes write failed: {e}")))?;
240            file.sync_all()
241                .map_err(|e| crate::core::DMSCError::Other(format!("atomic_write_bytes sync failed: {e}")))?;
242        }
243
244        fs::rename(&tmp_path, path)
245            .map_err(|e| crate::core::DMSCError::Other(format!("atomic_write_bytes rename failed: {e}")))?;
246
247        Ok(())
248    }
249
250    /// Reads text from a file.
251    /// 
252    /// # Parameters
253    /// 
254    /// - `path`: The path to the file to read
255    /// 
256    /// # Returns
257    /// 
258    /// A `DMSCResult<String>` containing the file content
259    fn read_text(&self, path: &Path) -> DMSCResult<String> {
260        let mut file = OpenOptions::new()
261            .read(true)
262            .open(path)
263            .map_err(|e| crate::core::DMSCError::Other(format!("read_text open failed: {e}")))?;
264        let mut buf = String::new();
265        file.read_to_string(&mut buf)
266            .map_err(|e| crate::core::DMSCError::Other(format!("read_text read failed: {e}")))?;
267        Ok(buf)
268    }
269
270    /// Returns the application data directory.
271    /// 
272    /// Ensures the directory exists before returning it.
273    /// 
274    /// # Returns
275    /// 
276    /// The application data directory path
277    fn app_dir(&self) -> PathBuf {
278        let _ = fs::create_dir_all(&self.app_data_root);
279        self.app_data_root.clone()
280    }
281
282    /// Returns a category-specific directory.
283    /// 
284    /// Ensures the directory exists before returning it.
285    /// 
286    /// # Parameters
287    /// 
288    /// - `name`: The name of the category
289    /// 
290    /// # Returns
291    /// 
292    /// The category directory path
293    fn category_dir(&self, name: &str) -> PathBuf {
294        let dir = self.app_dir().join(name);
295        let _ = fs::create_dir_all(&dir);
296        dir
297    }
298}
299
300/// Public-facing filesystem class.
301/// 
302/// This struct provides a comprehensive file system abstraction for DMSC, offering safe and reliable
303/// file operations with support for atomic writes, directory management, and structured data formats.
304#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
305#[derive(Clone)]
306pub struct DMSCFileSystem {
307    /// Internal file system implementation
308    inner: FileSystemImpl,
309}
310
311impl DMSCFileSystem {
312    /// Creates a new file system with a project root and default app data root.
313    /// 
314    /// # Parameters
315    /// 
316    /// - `project_root`: The project root directory
317    /// 
318    /// # Returns
319    /// 
320    /// A new `DMSCFileSystem` instance
321    pub fn new_with_root(project_root: PathBuf) -> Self {
322        let inner = FileSystemImpl::new_with_root(project_root);
323        DMSCFileSystem { inner }
324    }
325
326    /// Creates a new file system with specified roots.
327    /// 
328    /// # Parameters
329    /// 
330    /// - `project_root`: The project root directory
331    /// - `app_data_root`: The application data root directory
332    /// 
333    /// # Returns
334    /// 
335    /// A new `DMSCFileSystem` instance
336    pub fn new_with_roots(project_root: PathBuf, app_data_root: PathBuf) -> Self {
337        let inner = FileSystemImpl::new_with_roots(project_root, app_data_root);
338        DMSCFileSystem { inner }
339    }
340
341    /// Creates a new file system with the current working directory as the project root.
342    /// 
343    /// # Returns
344    /// 
345    /// A `DMSCResult<Self>` containing the new `DMSCFileSystem` instance
346    pub fn new_auto_root() -> DMSCResult<Self> {
347        let cwd = std::env::current_dir()
348            .map_err(|e| crate::core::DMSCError::Other(format!("detect project root failed: {e}")))?;
349        Ok(Self::new_with_root(cwd))
350    }
351
352    /// Returns the project root directory.
353    /// 
354    /// # Returns
355    /// 
356    /// A reference to the project root path
357    pub fn project_root(&self) -> &Path {
358        self.inner.project_root()
359    }
360
361    /// Safely creates a directory and all its parent directories if they don't exist.
362    /// 
363    /// # Parameters
364    /// 
365    /// - `path`: The path to the directory to create
366    /// 
367    /// # Returns
368    /// 
369    /// A `DMSCResult<PathBuf>` containing the created directory path
370    pub fn safe_mkdir<P: AsRef<Path>>(&self, path: P) -> DMSCResult<PathBuf> {
371        self.inner.safe_mkdir(path.as_ref())
372    }
373
374    /// Ensures that the parent directory of a given path exists.
375    /// 
376    /// # Parameters
377    /// 
378    /// - `path`: The path whose parent directory should be ensured
379    /// 
380    /// # Returns
381    /// 
382    /// A `DMSCResult<PathBuf>` containing the parent directory path
383    pub fn ensure_parent_dir<P: AsRef<Path>>(&self, path: P) -> DMSCResult<PathBuf> {
384        self.inner.ensure_parent_dir(path.as_ref())
385    }
386
387    /// Atomically writes text to a file.
388    /// 
389    /// This method writes to a temporary file first, then renames it to the target path,
390    /// ensuring that the write operation is atomic.
391    /// 
392    /// # Parameters
393    /// 
394    /// - `path`: The path to the file to write
395    /// - `text`: The text to write to the file
396    /// 
397    /// # Returns
398    /// 
399    /// A `DMSCResult<()>` indicating success or failure
400    pub fn atomic_write_text<P: AsRef<Path>>(&self, path: P, text: &str) -> DMSCResult<()> {
401        self.inner.atomic_write_text(path.as_ref(), text)
402    }
403
404    /// Atomically writes bytes to a file.
405    /// 
406    /// This method writes to a temporary file first, then renames it to the target path,
407    /// ensuring that the write operation is atomic.
408    /// 
409    /// # Parameters
410    /// 
411    /// - `path`: The path to the file to write
412    /// - `data`: The bytes to write to the file
413    /// 
414    /// # Returns
415    /// 
416    /// A `DMSCResult<()>` indicating success or failure
417    pub fn atomic_write_bytes<P: AsRef<Path>>(&self, path: P, data: &[u8]) -> DMSCResult<()> {
418        self.inner.atomic_write_bytes(path.as_ref(), data)
419    }
420
421    /// Reads text from a file.
422    /// 
423    /// # Parameters
424    /// 
425    /// - `path`: The path to the file to read
426    /// 
427    /// # Returns
428    /// 
429    /// A `DMSCResult<String>` containing the file content
430    pub fn read_text<P: AsRef<Path>>(&self, path: P) -> DMSCResult<String> {
431        self.inner.read_text(path.as_ref())
432    }
433
434    /// Reads JSON from a file and deserializes it into a type.
435    /// 
436    /// # Parameters
437    /// 
438    /// - `path`: The path to the JSON file to read
439    /// 
440    /// # Type Parameters
441    /// 
442    /// - `T`: The type to deserialize the JSON into
443    /// 
444    /// # Returns
445    /// 
446    /// A `DMSCResult<T>` containing the deserialized data
447    pub fn read_json<P: AsRef<Path>, T: DeserializeOwned>(&self, path: P) -> DMSCResult<T> {
448        let text = self.read_text(path)?;
449        serde_json::from_str(&text)
450            .map_err(|e| crate::core::DMSCError::Other(format!("json read failed: {e}")))
451    }
452
453    /// Checks if a file or directory exists.
454    /// 
455    /// # Parameters
456    /// 
457    /// - `path`: The path to check
458    /// 
459    /// # Returns
460    /// 
461    /// `true` if the path exists, `false` otherwise
462    pub fn exists<P: AsRef<Path>>(&self, path: P) -> bool {
463        path.as_ref().exists()
464    }
465
466    /// Removes a file.
467    /// 
468    /// # Parameters
469    /// 
470    /// - `path`: The path to the file to remove
471    /// 
472    /// # Returns
473    /// 
474    /// A `DMSCResult<()>` indicating success or failure
475    pub fn remove_file<P: AsRef<Path>>(&self, path: P) -> DMSCResult<()> {
476        let p = path.as_ref();
477        match fs::remove_file(p) {
478            Ok(()) => Ok(()),
479            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
480            Err(e) => Err(crate::core::DMSCError::Other(format!("remove_file failed: {e}"))),
481        }
482    }
483
484    /// Removes a directory and all its contents.
485    /// 
486    /// # Parameters
487    /// 
488    /// - `path`: The path to the directory to remove
489    /// 
490    /// # Returns
491    /// 
492    /// A `DMSCResult<()>` indicating success or failure
493    pub fn remove_dir_all<P: AsRef<Path>>(&self, path: P) -> DMSCResult<()> {
494        let p = path.as_ref();
495        match fs::remove_dir_all(p) {
496            Ok(()) => Ok(()),
497            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
498            Err(e) => Err(crate::core::DMSCError::Other(format!("remove_dir_all failed: {e}"))),
499        }
500    }
501
502    /// Copies a file from one path to another.
503    /// 
504    /// # Parameters
505    /// 
506    /// - `from`: The source file path
507    /// - `to`: The destination file path
508    /// 
509    /// # Returns
510    /// 
511    /// A `DMSCResult<()>` indicating success or failure
512    pub fn copy_file<P: AsRef<Path>, Q: AsRef<Path>>(&self, from: P, to: Q) -> DMSCResult<()> {
513        let src = from.as_ref();
514        let dst = to.as_ref();
515        if let Some(parent) = dst.parent() {
516            self.safe_mkdir(parent)?;
517        }
518        fs::copy(src, dst)
519            .map_err(|e| crate::core::DMSCError::Other(format!("copy_file failed: {e}")))?;
520        Ok(())
521    }
522
523    /// Appends text to a file.
524    /// 
525    /// # Parameters
526    /// 
527    /// - `path`: The path to the file to append to
528    /// - `text`: The text to append to the file
529    /// 
530    /// # Returns
531    /// 
532    /// A `DMSCResult<()>` indicating success or failure
533    pub fn append_text<P: AsRef<Path>>(&self, path: P, text: &str) -> DMSCResult<()> {
534        use std::io::Write as _;
535
536        let path_ref = path.as_ref();
537        self.ensure_parent_dir(path_ref)?;
538        let mut file = OpenOptions::new()
539            .create(true)
540            .append(true)
541            .open(path_ref)
542            .map_err(|e| crate::core::DMSCError::Other(format!("append_text open failed: {e}")))?;
543        file.write_all(text.as_bytes())
544            .map_err(|e| crate::core::DMSCError::Other(format!("append_text write failed: {e}")))?;
545        file.flush()
546            .map_err(|e| crate::core::DMSCError::Other(format!("append_text flush failed: {e}")))?;
547        Ok(())
548    }
549
550    /// Writes a JSON value to a file.
551    /// 
552    /// # Parameters
553    /// 
554    /// - `path`: The path to the file to write
555    /// - `value`: The value to serialize and write
556    /// 
557    /// # Type Parameters
558    /// 
559    /// - `T`: The type of the value to serialize
560    /// 
561    /// # Returns
562    /// 
563    /// A `DMSCResult<()>` indicating success or failure
564    pub fn write_json<P: AsRef<Path>, T: Serialize>(&self, path: P, value: &T) -> DMSCResult<()> {
565        let text = serde_json::to_string_pretty(value)
566            .map_err(|e| crate::core::DMSCError::Other(format!("json serialize failed: {e}")))?;
567        self.atomic_write_text(path, &text)
568    }
569
570    /// Returns the application data directory.
571    /// 
572    /// # Returns
573    /// 
574    /// The application data directory path
575    pub fn app_dir(&self) -> PathBuf {
576        self.inner.app_dir()
577    }
578
579    /// Returns the logs directory.
580    /// 
581    /// # Returns
582    /// 
583    /// The logs directory path
584    pub fn logs_dir(&self) -> PathBuf {
585        self.inner.category_dir("logs")
586    }
587
588    /// Returns the cache directory.
589    /// 
590    /// # Returns
591    /// 
592    /// The cache directory path
593    pub fn cache_dir(&self) -> PathBuf {
594        self.inner.category_dir("cache")
595    }
596
597    /// Returns the reports directory.
598    /// 
599    /// # Returns
600    /// 
601    /// The reports directory path
602    pub fn reports_dir(&self) -> PathBuf {
603        self.inner.category_dir("reports")
604    }
605
606    /// Returns the observability directory.
607    /// 
608    /// # Returns
609    /// 
610    /// The observability directory path
611    pub fn observability_dir(&self) -> PathBuf {
612        self.inner.category_dir("observability")
613    }
614
615    /// Returns the temporary directory.
616    /// 
617    /// # Returns
618    /// 
619    /// The temporary directory path
620    pub fn temp_dir(&self) -> PathBuf {
621        self.inner.category_dir("tmp")
622    }
623
624    /// Ensures a path exists under a specific category directory.
625    /// 
626    /// # Parameters
627    /// 
628    /// - `category`: The category name ("logs", "cache", "reports", "observability", "tmp", or other)
629    /// - `path_or_name`: The path or name to ensure under the category directory
630    /// 
631    /// # Returns
632    /// 
633    /// The full path to the ensured file or directory
634    pub fn ensure_category_path<S: AsRef<str>, P: AsRef<Path>>(&self, category: S, path_or_name: P) -> PathBuf {
635        let base = match category.as_ref() {
636            "logs" => self.logs_dir(),
637            "cache" => self.cache_dir(),
638            "reports" => self.reports_dir(),
639            "observability" => self.observability_dir(),
640            "tmp" => self.temp_dir(),
641            _ => self.app_dir(),
642        };
643
644        let target = base.join(path_or_name.as_ref());
645        let _ = fs::create_dir_all(target.parent().unwrap_or(&base));
646        target
647    }
648
649    /// Normalizes a path under a specific category directory, using only the file name.
650    /// 
651    /// # Parameters
652    /// 
653    /// - `category`: The category name ("logs", "cache", "reports", "observability", "tmp", or other)
654    /// - `path_or_name`: The path or name to normalize
655    /// 
656    /// # Returns
657    /// 
658    /// The full path to the normalized file or directory
659    pub fn normalize_under_category<S: AsRef<str>, P: AsRef<Path>>(&self, category: S, path_or_name: P) -> PathBuf {
660        let name = path_or_name.as_ref().file_name().unwrap_or_else(|| std::ffi::OsStr::new(""));
661        self.ensure_category_path(category, PathBuf::from(name))
662    }
663}
664
665#[cfg(feature = "pyo3")]
666/// Python bindings for DMSCFileSystem
667#[pyo3::prelude::pymethods]
668impl DMSCFileSystem {
669    /// Create a new file system with a project root from Python
670    #[new]
671    fn py_new(project_root: String) -> Result<Self, pyo3::prelude::PyErr> {
672        let path = PathBuf::from(project_root);
673        Ok(Self::new_with_root(path))
674    }
675    
676    /// Create a new file system with current working directory as root from Python
677    #[staticmethod]
678    fn new_auto_root_py() -> Result<Self, pyo3::PyErr> {
679        Self::new_auto_root()
680            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to create filesystem: {e}")))
681    }
682    
683    #[pyo3(name = "atomic_write_text")]
684    fn atomic_write_text_impl(&self, path: String, text: String) -> Result<(), pyo3::PyErr> {
685        self.atomic_write_text(&path, &text)
686            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to write text: {e}")))
687    }
688    
689    #[pyo3(name = "atomic_write_bytes")]
690    fn atomic_write_bytes_impl(&self, path: String, data: Vec<u8>) -> Result<(), pyo3::PyErr> {
691        self.atomic_write_bytes(&path, &data)
692            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to write bytes: {e}")))
693    }
694    
695    #[pyo3(name = "read_text")]
696    fn read_text_impl(&self, path: String) -> Result<String, pyo3::PyErr> {
697        self.read_text(&path)
698            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to read text: {e}")))
699    }
700    
701    #[pyo3(name = "write_json")]
702    fn write_json_impl(&self, path: String, value: String) -> Result<(), pyo3::PyErr> {
703        let json_value: serde_json::Value = serde_json::from_str(&value)
704            .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("Invalid JSON: {e}")))?;
705        self.write_json(&path, &json_value)
706            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to write JSON: {e}")))
707    }
708    
709    #[pyo3(name = "read_json")]
710    fn read_json_impl(&self, path: String) -> Result<String, pyo3::PyErr> {
711        let json_value: serde_json::Value = self.read_json(&path)
712            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to read JSON: {e}")))?;
713        serde_json::to_string_pretty(&json_value)
714            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to serialize JSON: {e}")))
715    }
716    
717    #[pyo3(name = "exists")]
718    fn exists_impl(&self, path: String) -> bool {
719        self.exists(&path)
720    }
721    
722    #[pyo3(name = "remove_file")]
723    fn remove_file_impl(&self, path: String) -> Result<(), pyo3::PyErr> {
724        self.remove_file(&path)
725            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to remove file: {e}")))
726    }
727    
728    #[pyo3(name = "remove_dir_all")]
729    fn remove_dir_all_impl(&self, path: String) -> Result<(), pyo3::PyErr> {
730        self.remove_dir_all(&path)
731            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to remove directory: {e}")))
732    }
733    
734    #[pyo3(name = "copy_file")]
735    fn copy_file_impl(&self, from: String, to: String) -> Result<(), pyo3::PyErr> {
736        self.copy_file(&from, &to)
737            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to copy file: {e}")))
738    }
739    
740    #[pyo3(name = "append_text")]
741    fn append_text_impl(&self, path: String, text: String) -> Result<(), pyo3::PyErr> {
742        self.append_text(&path, &text)
743            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to append text: {e}")))
744    }
745    
746    #[pyo3(name = "safe_mkdir")]
747    fn safe_mkdir_impl(&self, path: String) -> Result<String, pyo3::PyErr> {
748        let result = self.safe_mkdir(&path)
749            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to create directory: {e}")))?;
750        Ok(result.to_string_lossy().to_string())
751    }
752    
753    #[pyo3(name = "ensure_parent_dir")]
754    fn ensure_parent_dir_impl(&self, path: String) -> Result<String, pyo3::PyErr> {
755        let result = self.ensure_parent_dir(&path)
756            .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to ensure parent directory: {e}")))?;
757        Ok(result.to_string_lossy().to_string())
758    }
759    
760    #[pyo3(name = "get_project_root")]
761    fn get_project_root_impl(&self) -> String {
762        self.project_root().to_string_lossy().to_string()
763    }
764    
765    #[pyo3(name = "get_app_dir")]
766    fn get_app_dir_impl(&self) -> String {
767        self.app_dir().to_string_lossy().to_string()
768    }
769    
770    #[pyo3(name = "get_logs_dir")]
771    fn get_logs_dir_impl(&self) -> String {
772        self.logs_dir().to_string_lossy().to_string()
773    }
774    
775    #[pyo3(name = "get_cache_dir")]
776    fn get_cache_dir_impl(&self) -> String {
777        self.cache_dir().to_string_lossy().to_string()
778    }
779    
780    #[pyo3(name = "get_reports_dir")]
781    fn get_reports_dir_impl(&self) -> String {
782        self.reports_dir().to_string_lossy().to_string()
783    }
784    
785    #[pyo3(name = "get_observability_dir")]
786    fn get_observability_dir_impl(&self) -> String {
787        self.observability_dir().to_string_lossy().to_string()
788    }
789    
790    #[pyo3(name = "get_temp_dir")]
791    fn get_temp_dir_impl(&self) -> String {
792        self.temp_dir().to_string_lossy().to_string()
793    }
794    
795    #[pyo3(name = "ensure_category_path")]
796    fn ensure_category_path_impl(&self, category: String, path_or_name: String) -> String {
797        self.ensure_category_path(&category, &path_or_name)
798            .to_string_lossy()
799            .to_string()
800    }
801    
802    #[pyo3(name = "normalize_under_category")]
803    fn normalize_under_category_impl(&self, category: String, path_or_name: String) -> String {
804        self.normalize_under_category(&category, &path_or_name)
805            .to_string_lossy()
806            .to_string()
807    }
808}