diff --git a/src/application/dtos/file_dto.rs b/src/application/dtos/file_dto.rs index 1fba6812..af3c0553 100755 --- a/src/application/dtos/file_dto.rs +++ b/src/application/dtos/file_dto.rs @@ -60,24 +60,30 @@ pub struct FileDto { impl From for FileDto { fn from(file: File) -> Self { - let name = file.name(); - let mime = file.mime_type(); - let size = file.size(); + // Consume the entity by moving all fields — zero heap allocations + // for id, name, path, folder_id, owner_id (previously 5× .to_string()). + let parts = file.into_parts(); + + let icon_class = Arc::from(icon_class_for(&parts.name, &parts.mime_type)); + let icon_special_class = Arc::from(icon_special_class_for(&parts.name, &parts.mime_type)); + let category = Arc::from(category_for(&parts.name, &parts.mime_type)); + let size_formatted = format_file_size(parts.size); + let mime_type = Arc::from(parts.mime_type.as_str()); Self { - id: file.id().to_string(), - name: name.to_string(), - path: file.path_string().to_string(), - size, - mime_type: Arc::from(mime), - folder_id: file.folder_id().map(String::from), - created_at: file.created_at(), - modified_at: file.modified_at(), - icon_class: Arc::from(icon_class_for(name, mime)), - icon_special_class: Arc::from(icon_special_class_for(name, mime)), - category: Arc::from(category_for(name, mime)), - size_formatted: format_file_size(size), - owner_id: file.owner_id().map(String::from), + id: parts.id, + name: parts.name, + path: parts.path_string, + size: parts.size, + mime_type, + folder_id: parts.folder_id, + created_at: parts.created_at, + modified_at: parts.modified_at, + icon_class, + icon_special_class, + category, + size_formatted, + owner_id: parts.owner_id, sort_date: None, } } diff --git a/src/domain/entities/file.rs b/src/domain/entities/file.rs index 93458330..67d337b1 100755 --- a/src/domain/entities/file.rs +++ b/src/domain/entities/file.rs @@ -3,6 +3,24 @@ use crate::domain::services::path_service::StoragePath; // Re-export entity errors from the centralized module pub use super::entity_errors::{FileError, FileResult}; +/// Owned parts of a [`File`] entity, produced by [`File::into_parts()`]. +/// +/// Consuming a `File` into `FileParts` **moves** every field without cloning, +/// eliminating 3-5 heap allocations that previously occurred when converting +/// `File → FileDto` via `.to_string()` on each getter. +pub struct FileParts { + pub id: String, + pub name: String, + pub storage_path: StoragePath, + pub path_string: String, + pub size: u64, + pub mime_type: String, + pub folder_id: Option, + pub created_at: u64, + pub modified_at: u64, + pub owner_id: Option, +} + /** * Represents a file in the system's domain model. * @@ -167,6 +185,25 @@ impl File { }) } + /// Consume the entity and return all fields by ownership. + /// + /// Use this when converting `File` into a DTO to avoid cloning + /// every `String` field (saves 3-5 heap allocations per file). + pub fn into_parts(self) -> FileParts { + FileParts { + id: self.id, + name: self.name, + storage_path: self.storage_path, + path_string: self.path_string, + size: self.size, + mime_type: self.mime_type, + folder_id: self.folder_id, + created_at: self.created_at, + modified_at: self.modified_at, + owner_id: self.owner_id, + } + } + // Getters pub fn id(&self) -> &str { &self.id