From c41e8068170fbdb4e58f5ce385ed081ad6658c08 Mon Sep 17 00:00:00 2001 From: davidemazzocchi Date: Mon, 20 Oct 2025 15:21:25 +0200 Subject: [PATCH] doc: improve documentation of publicly available functions, structs and enums --- 01.workspace/heave/src/str/catalog.rs | 162 +++++++++++++++++++---- 01.workspace/heave/src/str/comparison.rs | 13 ++ 01.workspace/heave/src/str/condition.rs | 9 ++ 01.workspace/heave/src/str/entity.rs | 9 ++ 01.workspace/heave/src/str/filter.rs | 16 +++ 5 files changed, 184 insertions(+), 25 deletions(-) diff --git a/01.workspace/heave/src/str/catalog.rs b/01.workspace/heave/src/str/catalog.rs index 52f8998..d11a016 100644 --- a/01.workspace/heave/src/str/catalog.rs +++ b/01.workspace/heave/src/str/catalog.rs @@ -11,32 +11,58 @@ pub struct O { pub(crate) items: HashMap, } impl Catalog { - /// Creates a new `Catalog` instance. + /// Creates a new, empty in-memory `Catalog` for the database at the given path. + /// + /// This method does not create the database file or connect to it. It only + /// initializes an empty catalog in memory. The database file will be accessed + /// when `init()`, `persist()`, or `load_*` methods are called. /// /// # Arguments /// - /// * `path` - The path to the SQLite database file. + /// * `path` - The path to the SQLite database file that this catalog will manage. /// /// # Returns /// - /// A new `Catalog` instance. + /// A new `Catalog` instance with an empty in-memory item cache. pub fn new(path: &str) -> Self { Self { path: String::from(path), ..O::default() } } - /// Initializes the database. + /// Initializes the database by creating the file and schema if they don't exist. + /// + /// This method interacts with the filesystem to ensure the database file and its + /// underlying tables are ready. It has no effect on the in-memory state of the + /// catalog. + /// + /// # Effects + /// + /// - **Database State:** Creates the SQLite file and required tables if they are not + /// present. If the file already exists, it does nothing. + /// - **In-Memory State:** This method does not alter the in-memory entity cache. pub fn init(&self) -> result::Result<(), FailedTo> { let path = path::Path::new(&self.path); sqlite::init::db(path).map_err(|_| FailedTo::InitDatabase)?; Ok(()) } - /// Inserts or updates a single object that implements the `EAV` trait into the catalog. + /// Inserts or updates an object in the in-memory catalog. + /// + /// This is a purely in-memory operation. The change will not be written to the + /// database until `persist()` is called. + /// + /// # Effects + /// + /// - **In-Memory State:** + /// - If the entity does not exist in the catalog, it is added with the state + /// `EntityState::New`. + /// - If the entity already exists, it is overwritten and its state is set to + /// `EntityState::Updated`. + /// - **Database State:** Unchanged. Call `persist()` to save the changes. /// /// # Arguments /// - /// * `object` - The object to insert. + /// * `object` - The object to insert or update, which must implement the `EAV` trait. pub fn upsert(&mut self, object: impl EAV) -> Result<(), FailedTo> { let mut entity = object.try_into().map_err(|_| FailedTo::ConvertObject)?; if self.items.contains_key(&entity.id) { @@ -47,18 +73,29 @@ impl Catalog { self.items.insert(entity.id.clone(), entity); Ok(()) } - /// Inserts multiple objects that implement the `EAV` trait into the catalog. + /// Inserts or updates multiple objects in the in-memory catalog. + /// + /// This method calls `upsert()` for each object in the provided vector. Like + /// `upsert()`, this is a purely in-memory operation. + /// + /// # Effects + /// + /// - **In-Memory State:** Adds or updates multiple entities in the cache. + /// - **Database State:** Unchanged. Call `persist()` to save the changes. /// /// # Arguments /// - /// * `objects` - A vector of objects to insert. + /// * `objects` - A vector of objects to insert or update. pub fn insert_many(&mut self, objects: Vec) -> Result<(), FailedTo> { for object in objects { self.upsert(object)?; } Ok(()) } - /// Retrieves an entity by its ID and converts it into a specified type `T`. + /// Retrieves an entity by its ID from the in-memory catalog. + /// + /// This is a purely in-memory operation and does not interact with the database. + /// It will only find entities that have been loaded into or created in the catalog. /// /// # Arguments /// @@ -66,7 +103,8 @@ impl Catalog { /// /// # Returns /// - /// An `Option` containing the converted entity if found, otherwise `None`. + /// An `Option` containing the converted entity if found in the in-memory + /// cache, otherwise `None`. pub fn get(&self, id: &str) -> Result, FailedTo> where T: EAV, @@ -76,7 +114,10 @@ impl Catalog { .map(|e| T::try_from(e.clone()).map_err(|_| FailedTo::ConvertEntity)) .transpose() } - /// Retrieves the first entity that matches a given attribute and value. + /// Retrieves the first entity from the in-memory catalog that matches a given + /// class, attribute, and value. + /// + /// This is a purely in-memory operation and does not interact with the database. /// /// # Arguments /// @@ -85,7 +126,8 @@ impl Catalog { /// /// # Returns /// - /// An `Option` containing the converted entity if found, otherwise `None`. + /// An `Option` containing the first matching converted entity if found, + /// otherwise `None`. pub fn get_by_class_and_attribute( &self, attribute: &str, @@ -103,11 +145,13 @@ impl Catalog { .map(|item| T::try_from(item.clone()).map_err(|_| FailedTo::ConvertEntity)); items.next().transpose() } - /// Returns an iterator over entities of a specific class. + /// Returns an iterator over all entities of a specific class in the in-memory catalog. + /// + /// This is a purely in-memory operation and does not interact with the database. /// /// # Returns /// - /// An iterator that yields items of type `T`. + /// An iterator that yields items of type `T` from the in-memory cache. pub fn list_by_class(&self) -> impl Iterator> where T: EAV, @@ -117,7 +161,10 @@ impl Catalog { .filter(move |item| item.class == T::class()) .map(|item| T::try_from(item.clone()).map_err(|_| FailedTo::ConvertEntity)) } - /// Returns an iterator over entities that match a given attribute and value. + /// Returns an iterator over entities in the in-memory catalog that match a given + /// class, attribute, and value. + /// + /// This is a purely in-memory operation and does not interact with the database. /// /// # Arguments /// @@ -126,7 +173,7 @@ impl Catalog { /// /// # Returns /// - /// An iterator that yields items of type `T`. + /// An iterator that yields matching items of type `T` from the in-memory cache. pub fn list_by_class_and_attribute( &self, attribute: &str, @@ -142,21 +189,44 @@ impl Catalog { .filter(move |item| item.value_of(attribute) == Some(&value)) .map(|item| T::try_from(item.clone()).map_err(|_| FailedTo::ConvertEntity)) } - /// Schedules an entity for deletion. Actual delition will take place when 'persist' is called. + /// Marks an entity for deletion from the in-memory catalog. + /// + /// This is a purely in-memory operation. The entity will not be removed from the + /// database until `persist()` is called. + /// + /// # Effects + /// + /// - **In-Memory State:** If the entity exists, its state is set to + /// `EntityState::ToDelete`. If it was in a `New` state, it will simply be + /// forgotten upon the next `persist()` call without ever touching the database. + /// - **Database State:** Unchanged. Call `persist()` to apply the deletion. /// /// # Arguments /// - /// * `id` - The ID of the entity to delete. + /// * `id` - The ID of the entity to mark for deletion. pub fn delete(&mut self, id: &str) { let entity = self.items.get_mut(id); if let Some(entity) = entity { entity.state = EntityState::ToDelete; } } - /// Persists the current state of the catalog to the database. + /// Persists all in-memory changes to the database and updates the in-memory state. /// - /// - new entities will be written onto DB - /// - marked for delition entities will be deleted + /// This method synchronizes the state of the in-memory catalog with the database + /// by writing all pending changes (new, updated, and deleted entities). + /// + /// # Effects + /// + /// - **Database State:** + /// - Entities marked as `EntityState::New` are inserted into the database. + /// - Entities marked as `EntityState::Updated` are updated in the database. + /// - Entities marked as `EntityState::ToDelete` are deleted from the database. + /// + /// - **In-Memory State:** + /// - After a successful database write, entities marked `ToDelete` are permanently + /// removed from the in-memory catalog. + /// - All other entities that were successfully persisted (new or updated) have + /// their state changed to `EntityState::Loaded`. pub fn persist(&mut self) -> result::Result<(), FailedTo> { let path = path::Path::new(&self.path); sqlite::persist::catalog(path, self).map_err(|_| FailedTo::PersistCatalog)?; @@ -176,7 +246,21 @@ impl Catalog { .collect(); Ok(()) } - /// Loads an entity by its ID from the database into the catalog. + /// Loads a single entity by its ID from the database into the in-memory catalog. + /// + /// This method fetches the entity from the database and updates the in-memory + /// catalog. If an entity with the same ID already exists in memory, it will be + /// **overwritten** with the version from the database. + /// + /// # Effects + /// + /// - **In-Memory State:** + /// - If an entity is found in the database, it is inserted or updated in the + /// in-memory cache with the state `EntityState::Loaded`. + /// - Any existing in-memory entity with the same ID, regardless of its state + /// (`New`, `Updated`), will be replaced. + /// - If no entity is found in the database, the in-memory cache is not modified. + /// - **Database State:** Unchanged. /// /// # Arguments /// @@ -189,7 +273,19 @@ impl Catalog { } Ok(()) } - /// Loads all entities of a specific class from the database into the catalog. + /// Loads all entities of a specific class from the database into the in-memory catalog. + /// + /// This method fetches all entities matching the given class from the database. + /// If any of the loaded entities have IDs that match entities already in the + /// in-memory catalog, the in-memory versions will be **overwritten**. + /// + /// # Effects + /// + /// - **In-Memory State:** + /// - All found entities are inserted or updated in the in-memory cache with the + /// state `EntityState::Loaded`. + /// - Any existing in-memory entities with matching IDs are replaced. + /// - **Database State:** Unchanged. pub fn load_by_class(&mut self) -> Result<(), FailedTo> where T: EAV, @@ -202,8 +298,24 @@ impl Catalog { } Ok(()) } - /// Loads all entities matching the filter values - /// Different entity class with clashing attribute names might be loaded + /// Loads entities from the database that match a given `Filter`. + /// + /// This method queries the database for all entities that satisfy the conditions + /// specified in the filter. If any of the loaded entities have IDs that match + /// entities already in the in-memory catalog, the in-memory versions will be + /// **overwritten**. + /// + /// **Warning:** The filter is applied at the attribute level. If different entity + /// classes share attribute names, this method may load entities of multiple + /// classes. + /// + /// # Effects + /// + /// - **In-Memory State:** + /// - All found entities are inserted or updated in the in-memory cache with the + /// state `EntityState::Loaded`. + /// - Any existing in-memory entities with matching IDs are replaced. + /// - **Database State:** Unchanged. pub fn load_by_filter(&mut self, filter: &Filter) -> Result<(), FailedTo> { let path = path::Path::new(&self.path); let entities = sqlite::load::by_filter(path, filter).map_err(|_| FailedTo::LoadFromDB)?; diff --git a/01.workspace/heave/src/str/comparison.rs b/01.workspace/heave/src/str/comparison.rs index 2c978c6..af71393 100644 --- a/01.workspace/heave/src/str/comparison.rs +++ b/01.workspace/heave/src/str/comparison.rs @@ -1,12 +1,25 @@ +/// Defines the comparison operators used in a `Filter` condition. +/// +/// These operators are used to compare attribute values in the database when +/// loading entities via `load_by_filter`. #[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Hash)] pub enum E { + /// Exact equality (`==`). Applicable to all value types. Equal, + /// Greater than (`>`). Applicable to numeric types. Greater, + /// Greater than or equal to (`>=`). Applicable to numeric types. GreaterOrEqual, + /// Lesser than (`<`). Applicable to numeric types. Lesser, + /// Lesser than or equal to (`<=`). Applicable to numeric types. LesserOrEqual, + /// Case-insensitive exact match. Applicable to text values. IsExactly, + /// Case-insensitive prefix search (`LIKE 'value%'`). Applicable to text values. StartsWith, + /// Case-insensitive suffix search (`LIKE '%value'`). Applicable to text values. EndsWith, + /// Case-insensitive substring search (`LIKE '%value%'`). Applicable to text values. Contains, } diff --git a/01.workspace/heave/src/str/condition.rs b/01.workspace/heave/src/str/condition.rs index 23682e3..7615419 100644 --- a/01.workspace/heave/src/str/condition.rs +++ b/01.workspace/heave/src/str/condition.rs @@ -1,8 +1,17 @@ +/// Represents the value part of a `Filter` condition. +/// +/// Each variant holds a specific data type to be used in a comparison when +/// querying the database. #[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] pub enum E<'a> { + /// A boolean value (`true` or `false`). Bool(bool), + /// A floating-point number (`f64`). Real(f64), + /// A signed integer (`i64`). SignedInt(i64), + /// A text value (`&str`). Text(&'a str), + /// An unsigned integer (`u64`), stored as `i64` for SQLite compatibility. UnsignedInt(i64), } diff --git a/01.workspace/heave/src/str/entity.rs b/01.workspace/heave/src/str/entity.rs index a816ce7..c761ace 100644 --- a/01.workspace/heave/src/str/entity.rs +++ b/01.workspace/heave/src/str/entity.rs @@ -3,10 +3,19 @@ use crate::*; /// Represents a generic entity with an ID, class, and a set of attributes. #[derive(Debug, PartialEq, Clone)] pub struct O { + /// The unique identifier for this entity. pub id: String, + /// The current state of the entity within the `Catalog`'s in-memory cache. + /// This tracks whether the entity is new, modified, or marked for deletion. pub state: EntityState, + /// An optional timestamp or version number, typically used for optimistic + /// locking or tracking when the entity was last referenced or modified. pub ref_date: Option, + /// A string identifying the "type" or "class" of the entity (e.g., "product", "user"). + /// This is used to group and query entities of the same kind. pub class: String, + /// A map of attribute names to `Attribute` values, holding the actual data + /// of the entity. pub attributes: std::collections::HashMap, } diff --git a/01.workspace/heave/src/str/filter.rs b/01.workspace/heave/src/str/filter.rs index 016b067..3a96fde 100644 --- a/01.workspace/heave/src/str/filter.rs +++ b/01.workspace/heave/src/str/filter.rs @@ -1,16 +1,25 @@ use crate::*; +/// A builder for creating complex queries to load entities from the database. +/// +/// A `Filter` consists of one or more conditions that are combined with a logical AND. +/// It is used with `Catalog::load_by_filter` to retrieve entities that match +/// all specified criteria. #[derive(Debug, Default, PartialEq, Clone)] pub struct O<'a> { conditions: Vec<(String, Comparison, Condition<'a>)>, } impl<'a> Filter<'a> { + /// Creates a new, empty `Filter`. pub fn new() -> Self { Self { conditions: Vec::new(), } } + /// Adds a boolean condition to the filter. + /// + /// This is a shorthand for `with_bool(name, Comparison::Equal, value)`. pub fn with_bool(mut self, attribute_name: &str, value: bool) -> Self { self.conditions.push(( attribute_name.to_string(), @@ -19,6 +28,7 @@ impl<'a> Filter<'a> { )); self } + /// Adds a signed integer (`i64`) condition to the filter. pub fn with_signed_int( mut self, attribute_name: &str, @@ -32,6 +42,7 @@ impl<'a> Filter<'a> { )); self } + /// Adds an unsigned integer (`u64`) condition to the filter. pub fn with_unsigned_int( mut self, attribute_name: &str, @@ -45,6 +56,7 @@ impl<'a> Filter<'a> { )); self } + /// Adds a text (`&str`) condition to the filter. pub fn with_text( mut self, attribute_name: &str, @@ -58,6 +70,7 @@ impl<'a> Filter<'a> { )); self } + /// Adds a real number (`f64`) condition to the filter. pub fn with_real(mut self, attribute_name: &str, comparison: Comparison, value: f64) -> Self { self.conditions.push(( attribute_name.to_string(), @@ -66,6 +79,9 @@ impl<'a> Filter<'a> { )); self } + /// Returns an iterator over the conditions in the filter. + /// + /// This is used internally by the persistence engine. pub(crate) fn conditions(&self) -> impl Iterator)> { self.conditions.iter() }