diff --git a/src/application/loaders/fs.rs b/src/application/loaders/fs.rs index 0d4315d..31a1e1f 100644 --- a/src/application/loaders/fs.rs +++ b/src/application/loaders/fs.rs @@ -1,3 +1,11 @@ +//! A module to load and iterate over books from a hierarchical directory structure. +//! +//! The `Loader` struct serves as the entry point for recursively traversing a directory tree, +//! parsing each file's contents into `Book` instances. `Loader` implements the `IntoIterator` +//! trait to provide a custom iterator (`LoaderIter`) capable of traversing directories and files. +//! +//! The `LoaderIter` struct manages the iteration process by maintaining a queue of directories +//! to traverse and a queue of pending `Book` objects to be returned. use crate::application::parsers; use crate::domain::book::Book; use std::collections::VecDeque; diff --git a/src/application/loaders/inotify.rs b/src/application/loaders/inotify.rs index 5ffc19c..ef9b4cd 100644 --- a/src/application/loaders/inotify.rs +++ b/src/application/loaders/inotify.rs @@ -1,3 +1,6 @@ +//! The `Loader` struct manages an `inotify` instance that monitors a specified directory +//! for file modifications and creations. It acts as a bridge between the filesystem events +//! and the higher-level representation of books within the application. use crate::application::parsers; use crate::domain::book::Book; use inotify::{Event, Inotify, WatchMask}; diff --git a/src/application/mod.rs b/src/application/mod.rs index 76e4f69..7141bae 100644 --- a/src/application/mod.rs +++ b/src/application/mod.rs @@ -1,4 +1,4 @@ -pub mod services; +mod services; mod loaders; mod parsers; mod config; diff --git a/src/application/parsers/fb2.rs b/src/application/parsers/fb2.rs index 61a9c36..594402f 100644 --- a/src/application/parsers/fb2.rs +++ b/src/application/parsers/fb2.rs @@ -7,6 +7,76 @@ use std::path::Path; use crate::domain::author; use crate::domain::book::Book; +/// Parses an XML file located at the given path and extracts information about books. +/// +/// This function processes the XML structure using a streaming XML reader to extract details +/// about books, including: +/// - Title +/// - Language +/// - Keywords +/// - Authors (including optional details like first name, last name, middle name, and nickname) +/// - Publication year +/// - Publisher +/// - Description +/// +/// # Arguments +/// +/// * `path` - A reference to the file path (`&Path`) of the XML file to parse. +/// +/// # Returns +/// +/// Returns a `Result` where: +/// - `Ok(Vec)` contains a vector of `Book` objects constructed from the parsed XML. +/// - `Err(String)` contains an error message if the parsing fails at any stage. +/// +/// # Errors +/// +/// Returns an error in the following scenarios: +/// - Unable to open the file specified by `path`. +/// - Malformed XML data in the file. +/// - Issues during data extraction, such as reading incomplete or invalid values. +/// +/// # Example +/// +/// ```ignore +/// use std::path::Path; +/// let path = Path::new("books.xml"); +/// match parse(&path) { +/// Ok(books) => { +/// for book in books { +/// println!("Book Title: {}", book.title); +/// } +/// }, +/// Err(err) => eprintln!("Failed to parse XML file: {}", err), +/// } +/// ``` +/// +/// # XML Structure +/// +/// The XML should follow a specific schema with the following relevant elements: +/// - ``: Title of the book. +/// - ``: Language of the book. +/// - ``: A comma-separated list of keywords/tags. +/// - ``: Contains subfields ``, ``, ``, or ``. +/// - ``: Year of publication. +/// - ``: Publisher's name. +/// - ``: Description or annotation of the book. +/// +/// # Notes +/// +/// - Author data is flexible; if a nickname exists, it will override other name details. +/// - The resulting `Vec` contains just one book object, as indicated in the implementation. +/// +/// # Dependencies +/// +/// This function depends on the following crates: +/// - `quick-xml`: For fast XML parsing. +/// - `uuid`: To generate a unique identifier for each book. +/// - `chrono`: To serialize the current timestamp as an RFC3339 string. +/// +/// # See Also +/// +/// `Book` structure, which represents the parsed data for an individual book. pub fn parse(path: &Path) -> Result, String> { let file = File::open(path).map_err(|e| e.to_string())?; let mut reader = Reader::from_reader(BufReader::new(file)); diff --git a/src/application/parsers/mod.rs b/src/application/parsers/mod.rs index 120f46b..640bb74 100644 --- a/src/application/parsers/mod.rs +++ b/src/application/parsers/mod.rs @@ -6,6 +6,12 @@ mod rs; mod fb2; mod zip; + +/// Error enumeration representing possible errors that can occur when parsing files. +/// +/// This enumeration has the following variants: +/// - `NotSupported`: Indicates that the file format or extension is not supported. +/// - `ParseError`: Contains a `String` representing the error message when a parsing process fails. #[derive(Debug)] pub enum Error { NotSupported, @@ -21,6 +27,48 @@ impl fmt::Display for Error { } } +/// Parses a file at the given path and attempts to convert its contents into a vector of `Book` objects. +/// +/// This function determines the file type based on its extension and delegates the parsing duties +/// to the appropriate module. Supported file extensions are: +/// - `.rs`: Processed by the `rs` module. +/// - `.fb2`: Processed by the `fb2` module. +/// - `.zip`: Processed by the `zip` module. +/// +/// If the file's extension is unsupported or missing, this function returns a `NotSupported` error. +/// +/// # Arguments +/// +/// * `path` - A reference to a `PathBuf` that represents the file path to be parsed. +/// +/// # Returns +/// +/// * `Ok(Vec)` - A vector of `Book` objects if the file was successfully parsed. +/// * `Err(Error)` - An error if the file could not be parsed, the parsing process encountered +/// an issue, or the file extension is not supported. +/// +/// # Errors +/// +/// - `Error::ParseError` - If the file parsing fails. +/// - `Error::NotSupported` - If the file's extension is unsupported or missing. +/// +/// # Examples +/// +/// ```ignore +/// use std::path::PathBuf; +/// +/// let path = PathBuf::from("example.rs"); +/// let books = parse(&path); +/// match books { +/// Ok(book_list) => println!("Parsed {} books.", book_list.len()), +/// Err(e) => println!("Failed to parse file: {:?}", e), +/// } +/// ``` +/// +/// # Notes +/// +/// Ensure that the appropriate parsers (`rs`, `fb2`, `zip`) are properly implemented +/// and handle all required logic for their respective file types to avoid unexpected errors. pub fn parse(path: &PathBuf) -> Result, Error> { match path.extension().and_then(|s| s.to_str()) { Some("rs") => rs::parse(path).map_err(Error::ParseError), diff --git a/src/application/parsers/rs.rs b/src/application/parsers/rs.rs index 8735498..49c5f7b 100644 --- a/src/application/parsers/rs.rs +++ b/src/application/parsers/rs.rs @@ -2,6 +2,44 @@ use crate::domain::author::Author; use crate::domain::book::Book; use std::path::PathBuf; +/// Parses a given file path into a vector containing a `Book` object. +/// +/// # Arguments +/// +/// * `path` - A reference to a `PathBuf` that represents the file path to be parsed. +/// +/// # Returns +/// +/// * `Result, String>` - +/// - On success, returns a `Vec` with a single `Book` object populated based on the input path. +/// - On failure, returns an error `String` describing the issue. +/// +/// The function performs the following steps: +/// +/// 1. Creates a new instance of `Book`. +/// 2. Sets the `title` of the `Book` to the string representation of the input path. +/// 3. Creates a new instance of `Author`. +/// 4. Sets the `first_name` of the `Author` to the string representation of the file extension of `path`. +/// 5. Pushes the `Author` into the `author` vector of the `Book`. +/// 6. Returns a `Vec` containing the newly created `Book`. +/// +/// # Panics +/// +/// The function will panic if the input path does not contain a file extension +/// (i.e., when `path.extension()` returns `None`). +/// +/// # Example +/// +/// ```ignore +/// use std::path::PathBuf; +/// +/// let path = PathBuf::from("example.txt"); +/// let books = parse(&path).unwrap(); +/// +/// assert_eq!(books.len(), 1); +/// assert_eq!(books[0].title, "example.txt"); +/// assert_eq!(books[0].author[0].first_name, "txt"); +/// ``` pub fn parse(path: &PathBuf) -> Result, String> { let mut book = Book::new(); diff --git a/src/application/parsers/zip.rs b/src/application/parsers/zip.rs index 66f7c1a..54adbce 100644 --- a/src/application/parsers/zip.rs +++ b/src/application/parsers/zip.rs @@ -5,6 +5,54 @@ use std::io::BufReader; use std::path::{Path, PathBuf}; use zip::ZipArchive; + +/// Parses a ZIP archive to extract a collection of `Book` objects. +/// +/// This function takes a path to a ZIP archive file, reads its contents, and processes +/// each file within the archive to extract `Book` objects using a custom parser. If any +/// errors occur during file access, archive extraction, or parsing, they are returned as +/// a `String`. On success, it returns a vector of `Book` objects contained in the archive. +/// +/// # Arguments +/// +/// * `path` - A reference to a `Path` representing the file system path to the ZIP archive. +/// +/// # Returns +/// +/// * `Ok(Vec)` - A vector containing the `Book` objects successfully parsed +/// from the files in the archive. +/// * `Err(String)` - An error message if any step in opening the file, reading the archive, +/// or parsing the files fails. +/// +/// # Errors +/// +/// This function returns an error in the following cases: +/// * If the ZIP file cannot be opened. +/// * If the ZIP archive cannot be read. +/// * If an individual file within the archive cannot be accessed. +/// * If the parsing of a file fails. +/// +/// # Example +/// +/// ```ignore +/// use std::path::Path; +/// use your_crate::parse; +/// +/// let path = Path::new("books_archive.zip"); +/// match parse(&path) { +/// Ok(books) => { +/// for book in books { +/// println!("Parsed book: {:?}", book); +/// } +/// } +/// Err(e) => eprintln!("Failed to parse books: {}", e), +/// } +/// ``` +/// +/// # Dependencies +/// +/// This function relies on the `ZipArchive` for working with ZIP files and a `parsers` +/// module for custom file parsing logic. pub fn parse(path: &Path) -> Result, String> { let file = File::open(path).map_err(|e| e.to_string())?; let reader = BufReader::new(file); diff --git a/src/infrastructure/repository/inmem/books.rs b/src/infrastructure/repository/inmem/books.rs index ecedf50..29aa927 100644 --- a/src/infrastructure/repository/inmem/books.rs +++ b/src/infrastructure/repository/inmem/books.rs @@ -1,3 +1,63 @@ +/*! +This module provides an in-memory implementation of a `Repository` for managing books, including their associated authors. +It provides mechanisms to add, update, retrieve, and filter books, along with handling relationships to authors. + +# Structs +- `Book`: Internal representation of a book for the in-memory repository. It includes fields for metadata such as ID, title, author IDs, language, description, tags, publisher, and timestamps. +- `BookRepository`: The main repository struct that provides book and author management. It supports CRUD operations and filtering over books and their metadata. + +# Traits +- Implements `Repository` trait to provide repository functionalities for books, such as adding, removing, updating, retrieving, and filtering. + +# Implementation Details + +## `Book` Struct +A lightweight representation of a `book::Book` entity used internally in the repository. +- It implements conversions for: + - **From**: Converts `book::Book` into `Book` by copying metadata and transforming author data. + - **Into**: Converts `Book` back into `book::Book`, reconstructing the related author information. + +## `BookRepository` Struct +An in-memory implementation of a `Repository` that manages books and their associated authors. +- Fields: + - `books`: A vector containing all stored books in the repository. + - `authors`: A HashMap storing authors by their UUID for quick lookups. + - `author_uniques`: A mapping of unique author identifiers to their corresponding UUIDs to ensure uniqueness while managing authors. + +- Methods: + - `new() -> Self`: Creates a new, empty `BookRepository`. + - `populate_authors(&self, book: &mut book::Book)`: Updates the provided book's author information with detailed metadata (e.g., names) from the repository. + - `extract_authors(&mut self, item: &mut book::Book)`: Extracts and stores unique authors from a given book into the repository. + +## Repository Trait Implementation +Implements the `Repository` trait for `BookRepository` to provide standard repository features: +- `add(&mut self, mut item: book::Book)`: Adds a new book to the repository. Extracts and persists author metadata. + - Skips adding a book if a book with the same ID already exists. +- `bulk_add(&mut self, items: Vec)`: Adds multiple books to the repository. + - Filters out duplicate books before adding. +- `remove(&mut self, item: book::Book)`: Removes a book from the repository by its ID. +- `get(&self, id: String) -> Option`: Retrieves a book by its ID. Populates the book's author information if it exists. +- `update(&mut self, mut item: book::Book)`: Updates an existing book. Extracts author details and persists changes. +- `filter(&self, f: BookFilter) -> Box>`: Filters books based on the criteria provided in the `BookFilter` struct and returns an iterator over matching books. + - Available filters: + - Title + - Description + - Language + - Tags + - Publisher + - Published date + - Last updated date + - Author (by ID or name) + +# Filtering Logic +Filters books based on the provided `BookFilter` parameters: +- Matches fields like title, description, language, tags, publisher, and dates. +- Supports author-based filtering, allowing searches using either author IDs or names. + +# Notes +- Author uniqueness is enforced using a mapping of unique identifiers to `Uuid` (`author_uniques` field) to manage duplicate or overlapping authors efficiently. +- Iterators are used for filtered results to allow efficient processing of the filtered data. +*/ use crate::domain::repository::{BookFilter, Repository}; use crate::domain::{author, book}; use std::collections::HashMap; diff --git a/src/lib.rs b/src/lib.rs index 6e51ddc..e894c75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ use crate::application::application::Application; pub mod domain; -mod application; +pub mod application; pub mod infrastructure; pub fn demo() -> Application {