perf: zero-alloc File → FileDto via into_parts() ownership transfer

- Add FileParts struct and File::into_parts() to yield owned fields
- Rewrite From<File> for FileDto to move id, name, path, folder_id,
  owner_id by ownership instead of .to_string() copies
- Eliminates ~5 heap allocations per file (5000 saved per 1000-file listing)
This commit is contained in:
Dionisio
2026-03-06 22:37:31 +01:00
parent d4a7cb0414
commit 55936699ce
2 changed files with 59 additions and 16 deletions
+22 -16
View File
@@ -60,24 +60,30 @@ pub struct FileDto {
impl From<File> for FileDto { impl From<File> for FileDto {
fn from(file: File) -> Self { fn from(file: File) -> Self {
let name = file.name(); // Consume the entity by moving all fields — zero heap allocations
let mime = file.mime_type(); // for id, name, path, folder_id, owner_id (previously 5× .to_string()).
let size = file.size(); 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 { Self {
id: file.id().to_string(), id: parts.id,
name: name.to_string(), name: parts.name,
path: file.path_string().to_string(), path: parts.path_string,
size, size: parts.size,
mime_type: Arc::from(mime), mime_type,
folder_id: file.folder_id().map(String::from), folder_id: parts.folder_id,
created_at: file.created_at(), created_at: parts.created_at,
modified_at: file.modified_at(), modified_at: parts.modified_at,
icon_class: Arc::from(icon_class_for(name, mime)), icon_class,
icon_special_class: Arc::from(icon_special_class_for(name, mime)), icon_special_class,
category: Arc::from(category_for(name, mime)), category,
size_formatted: format_file_size(size), size_formatted,
owner_id: file.owner_id().map(String::from), owner_id: parts.owner_id,
sort_date: None, sort_date: None,
} }
} }
+37
View File
@@ -3,6 +3,24 @@ use crate::domain::services::path_service::StoragePath;
// Re-export entity errors from the centralized module // Re-export entity errors from the centralized module
pub use super::entity_errors::{FileError, FileResult}; 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<String>,
pub created_at: u64,
pub modified_at: u64,
pub owner_id: Option<String>,
}
/** /**
* Represents a file in the system's domain model. * 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 // Getters
pub fn id(&self) -> &str { pub fn id(&self) -> &str {
&self.id &self.id