Cleaning up code and adding more documentation

This commit is contained in:
vandomej 2025-09-06 11:20:25 -07:00
parent 38d9ea7bce
commit c6ae9b71cd
5 changed files with 134 additions and 13 deletions

View file

@ -1,5 +1,11 @@
//! Constants used in the `file_linked` crate.
//! This module defines enums and constants that are used throughout the crate, such as data formats for serialization.
/// Data formats supported for serialization and deserialization.
#[derive(Debug)]
pub enum DataFormat {
/// Use the `bincode` format for serialization.
Bincode,
/// Use the `json` format for serialization.
Json,
}

View file

@ -1,11 +1,21 @@
//! Error types for the `file_linked` crate.
//! This module defines a unified error type for the crate, wrapping errors from dependencies and providing conversions
//! from common error types.
use thiserror::Error;
/// The main error type for the `file_linked` crate.
#[derive(Error, Debug)]
pub enum Error {
/// An error originating from the `bincode` crate.
#[error(transparent)]
Serialization(bincode::Error),
/// An IO error, such as file not found or permission denied.
#[error(transparent)]
IO(std::io::Error),
/// Any other error, wrapped using `anyhow`.
#[error(transparent)]
Other(#[from] anyhow::Error),
}

View file

@ -1,5 +1,61 @@
//! A wrapper around an object that ties it to a physical file
//! # file_linked
//!
//! **file_linked** is a Rust library for binding objects directly to files, providing persistent, file-backed state management for any serializable type. It is designed for use cases where you want to transparently synchronize in-memory data with a file, supporting both synchronous and asynchronous mutation patterns.
//!
//! ## Features
//! - Generic wrapper [`FileLinked<T>`] for any serializable type
//! - Automatic file synchronization on mutation or replacement
//! - Asynchronous and thread-safe access using `tokio::RwLock`
//! - Support for multiple serialization formats (JSON, Bincode)
//! - Simple API for reading, mutating, and replacing data
//! - Error handling with unified [`Error`] type
//!
//! ## Modules
//! - [`constants`]: Data format selection and related constants
//! - [`error`]: Error types for serialization, I/O, and general errors
//!
//! ## Example
//! ```rust,ignore
//! # use file_linked::*;
//! # use file_linked::constants::data_format::DataFormat;
//! # use serde::{Deserialize, Serialize};
//! # use std::path::PathBuf;
//! # use tokio;
//!
//! #[derive(Deserialize, Serialize)]
//! struct Test {
//! pub a: u32,
//! pub b: String,
//! pub c: f64
//! }
//!
//! #[tokio::main]
//! async fn main() {
//! let test = Test { a: 1, b: String::from("two"), c: 3.0 };
//! let mut linked = FileLinked::new(test, &PathBuf::from("./file"), DataFormat::Json).await.unwrap();
//! linked.mutate(|x| x.a += 1).await.unwrap();
//! assert_eq!(linked.readonly().read().await.a, 2);
//! }
//! ```
//!
//! ## Usage
//! 1. Create a serializable struct and instantiate a [`FileLinked`] object.
//! 2. Use `.readonly()` for shared access, `.mutate()` or `.replace()` for mutation (which automatically syncs to file).
//! 3. Use `.from_file()` to restore from disk.
//!
//! ## Crate Organization
//! - All core logic is in [`FileLinked`].
//! - Data format selection is in [`constants`].
//! - Error types and helpers are in [`error`].
//!
//! [`FileLinked`]: crate::FileLinked
//! [`Error`]: crate::error::Error
//! [`constants`]: crate::constants
//! [`error`]: crate::error
#![warn(missing_docs)]
/// Constants used in the `file_linked` crate.
pub mod constants;
pub mod error;
@ -236,12 +292,14 @@ where
/// let mut linked_test = FileLinked::new(test, &PathBuf::from("./temp"), DataFormat::Bincode).await
/// .expect("Unable to create file linked object");
///
/// // Scope to limit the lifetime of the read lock
/// {
/// let readonly = linked_test.readonly();
/// let readonly_ref = readonly.read().await;
/// assert_eq!(readonly_ref.a, 1);
/// }
///
/// // After mutate closure completes the object will be written to a file
/// linked_test.mutate(|t| t.a = 2).await?;
///
/// let readonly = linked_test.readonly();
@ -332,6 +390,52 @@ where
T: Serialize + DeserializeOwned + Send + 'static,
{
/// Asynchronously modifies the data contained in a `FileLinked` object using an async callback `op`.
/// The callback `op` is given an `Arc<RwLock<T>>` to the underlying data, allowing for concurrent access patterns.
/// After the async operation completes the data is written to a file to synchronize the state.
///
/// # Examples
/// ```rust
/// # use file_linked::*;
/// # use file_linked::error::Error;
/// # use file_linked::constants::data_format::DataFormat;
/// # use serde::{Deserialize, Serialize};
/// # use std::path::PathBuf;
/// # use tokio;
/// #
/// # #[derive(Deserialize, Serialize)]
/// # struct Test {
/// # pub a: u32,
/// # pub b: String,
/// # pub c: f64
/// # }
/// # #[tokio::main]
/// # async fn main() -> Result<(), Error> {
/// let test = Test {
/// a: 1,
/// b: String::from(""),
/// c: 0.0
/// };
/// let mut linked_test = FileLinked::new(test, &PathBuf::from("./temp"), DataFormat::Bincode).await
/// .expect("Unable to create file linked object");
///
/// // Scope to limit the lifetime of the read lock
/// {
/// let readonly = linked_test.readonly();
/// let readonly_ref = readonly.read().await;
/// assert_eq!(readonly_ref.a, 1);
/// }
///
/// // After mutate_async closure completes the object will be written to a file
/// linked_test.mutate_async(|t| async move { t.write().await.a = 2 }).await?;
///
/// let readonly = linked_test.readonly();
/// let readonly_ref = readonly.read().await;
/// assert_eq!(readonly_ref.a, 2);
/// # drop(linked_test);
/// # std::fs::remove_file("./temp").expect("Unable to remove file");
/// # Ok(())
/// # }
/// ```
pub async fn mutate_async<F, Fut, U>(&mut self, op: F) -> Result<U, Error>
where
F: FnOnce(Arc<RwLock<T>>) -> Fut,

View file

@ -7,13 +7,13 @@ mod test_state;
use anyhow::Result;
use clap::Parser;
use test_state::TestState;
use file_linked::constants::data_format::DataFormat;
use gemla::{
core::{Gemla, GemlaConfig},
error::log_error,
};
use std::{path::PathBuf, time::Instant};
use test_state::TestState;
// const NUM_THREADS: usize = 2;

View file

@ -62,13 +62,13 @@ pub struct GeneticNodeContext<S> {
/// // ...
/// }
/// ```
///
///
/// [`Gemla`]: crate::Gemla
#[async_trait]
pub trait GeneticNode: Send {
/// Custom type that provides a shared context across different nodes and simulations. Useful if you want to manage
/// conncurrency or share data between nodes.
///
///
/// # Example
/// ```ignore
/// pub struct SharedContext {
@ -89,7 +89,7 @@ pub trait GeneticNode: Send {
/// * A boxed instance of the initialized node.
///
/// # Example
/// ```rust
/// ```rust,ignore
/// # use gemla::core::genetic_node::{GeneticNode, GeneticNodeContext};
/// # use async_trait::async_trait;
/// # use serde::{Serialize, Deserialize};
@ -100,7 +100,7 @@ pub trait GeneticNode: Send {
/// population: Vec<i64>,
/// max_generations: u64,
/// }
///
///
/// #[async_trait]
/// impl GeneticNode for TestState {
/// type Context = ();
@ -125,7 +125,7 @@ pub trait GeneticNode: Send {
/// * `Ok(true)` if the node should continue to the next phase, `Ok(false)` if finished.
///
/// # Example
/// ```rust
/// ```rust,ignore
/// # use gemla::core::genetic_node::{GeneticNode, GeneticNodeContext};
/// # use async_trait::async_trait;
/// # use serde::{Serialize, Deserialize};
@ -136,7 +136,7 @@ pub trait GeneticNode: Send {
/// population: Vec<i64>,
/// max_generations: u64,
/// }
///
///
/// #[async_trait]
/// impl GeneticNode for TestState {
/// type Context = ();
@ -152,7 +152,8 @@ pub trait GeneticNode: Send {
/// // ...
/// }
/// ```
async fn simulate(&mut self, context: GeneticNodeContext<Self::Context>) -> Result<bool, Error>;
async fn simulate(&mut self, context: GeneticNodeContext<Self::Context>)
-> Result<bool, Error>;
/// Mutates members in a population and/or crossbreeds them to produce new offspring.
///
@ -164,7 +165,7 @@ pub trait GeneticNode: Send {
/// * `Err(Error)` if an error occurred during mutation.
///
/// # Example
/// ```rust
/// ```rust,ignore
/// # use gemla::core::genetic_node::{GeneticNode, GeneticNodeContext};
/// # use async_trait::async_trait;
/// # use serde::{Serialize, Deserialize};
@ -175,7 +176,7 @@ pub trait GeneticNode: Send {
/// population: Vec<i64>,
/// max_generations: u64,
/// }
///
///
/// #[async_trait]
/// impl GeneticNode for TestState {
/// type Context = ();
@ -213,7 +214,7 @@ pub trait GeneticNode: Send {
/// * `context` - The context for merging.
///
/// # Example
/// ```rust
/// ```rust,ignore
/// # use gemla::core::genetic_node::{GeneticNode, GeneticNodeContext};
/// # use async_trait::async_trait;
/// # use serde::{Serialize, Deserialize};
@ -223,7 +224,7 @@ pub trait GeneticNode: Send {
/// population: Vec<i64>,
/// max_generations: u64,
/// }
///
///
/// #[async_trait]
/// impl GeneticNode for TestState {
/// type Context = ();