use std::time::SystemTime; use crate::{BookmarkId, DomainError, ProfileId, SpaceId, UrlText}; #[derive(Clone, Debug, Eq, PartialEq)] pub struct BookmarkEntry { id: BookmarkId, profile_id: ProfileId, space_id: SpaceId, collection_name: String, title: String, url: UrlText, tags: Vec, note: Option, thumbnail_key: Option, added_at: SystemTime, } impl BookmarkEntry { pub fn new( profile_id: ProfileId, space_id: SpaceId, collection_name: impl Into, title: impl Into, url: UrlText, added_at: SystemTime, ) -> Result { let collection_name = non_empty_text("bookmark collection", collection_name.into())?; let title = non_empty_text("bookmark title", title.into())?; Ok(Self { id: BookmarkId::new(), profile_id, space_id, collection_name, title, url, tags: Vec::new(), note: None, thumbnail_key: None, added_at, }) } #[must_use] pub fn id(&self) -> &BookmarkId { &self.id } #[must_use] pub fn profile_id(&self) -> &ProfileId { &self.profile_id } #[must_use] pub fn space_id(&self) -> &SpaceId { &self.space_id } #[must_use] pub fn collection_name(&self) -> &str { &self.collection_name } #[must_use] pub fn title(&self) -> &str { &self.title } #[must_use] pub fn url(&self) -> &UrlText { &self.url } #[must_use] pub fn display_url(&self) -> String { self.url.display_url() } #[must_use] pub fn tags(&self) -> &[String] { &self.tags } #[must_use] pub fn note(&self) -> Option<&str> { self.note.as_deref() } #[must_use] pub fn thumbnail_key(&self) -> Option<&str> { self.thumbnail_key.as_deref() } #[must_use] pub fn added_at(&self) -> SystemTime { self.added_at } pub fn set_collection_name( &mut self, collection_name: impl Into, ) -> Result<(), DomainError> { self.collection_name = non_empty_text("bookmark collection", collection_name.into())?; Ok(()) } pub fn set_tags(&mut self, tags: Vec) -> Result<(), DomainError> { self.tags = normalize_tags(tags)?; Ok(()) } pub fn set_note(&mut self, note: impl Into) -> Result<(), DomainError> { self.note = Some(non_empty_text("bookmark note", note.into())?); Ok(()) } pub fn clear_note(&mut self) { self.note = None; } pub fn set_thumbnail_key( &mut self, thumbnail_key: impl Into, ) -> Result<(), DomainError> { self.thumbnail_key = Some(non_empty_text("bookmark thumbnail key", thumbnail_key.into())?); Ok(()) } pub fn clear_thumbnail_key(&mut self) { self.thumbnail_key = None; } } fn non_empty_text(field: &'static str, value: String) -> Result { let trimmed = value.trim(); if trimmed.is_empty() { return Err(DomainError::EmptyField { field }); } Ok(trimmed.to_string()) } fn normalize_tags(tags: Vec) -> Result, DomainError> { tags.into_iter().map(|tag| non_empty_text("bookmark tag", tag)).collect() }