diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-06-19 23:42:19 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-06-19 23:42:19 +0800 |
| commit | 40183e431da97fe0377b2f2e2ea47b3a14376680 (patch) | |
| tree | 0c117e2ec707f3ad33206f1e320af2b439f80dbe | |
| parent | d36acf196f7475a91550cc64c9c9a545adb65c50 (diff) | |
feat(rola-bucket): add TOML bucket config read/write
Add functions to read and write BucketConfig from and to TOML files,
including space-aware variants. Replace hardcoded resource file with
serializable config struct.
| -rw-r--r-- | Cargo.lock | 67 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | rola-bucket/Cargo.toml | 1 | ||||
| -rw-r--r-- | rola-bucket/res/bucket.toml | 4 | ||||
| -rw-r--r-- | rola-bucket/src/bucket/config.rs | 83 | ||||
| -rw-r--r-- | rola-bucket/src/bucket/config/test.rs | 116 | ||||
| -rw-r--r-- | rola-bucket/src/bucket/init.rs | 8 | ||||
| -rw-r--r-- | rola-bucket/src/bucket/space.rs | 5 |
8 files changed, 262 insertions, 23 deletions
@@ -491,6 +491,7 @@ dependencies = [ "space-system", "thiserror", "tokio", + "toml 1.1.2+spec-1.1.0", ] [[package]] @@ -585,6 +586,15 @@ dependencies = [ ] [[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] name = "shakehand" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -594,7 +604,7 @@ dependencies = [ "proc-macro2", "quote", "syn", - "toml", + "toml 0.8.23", ] [[package]] @@ -726,12 +736,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_edit", ] [[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.3", +] + +[[package]] name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -741,6 +766,15 @@ dependencies = [ ] [[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -748,10 +782,19 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_write", - "winnow", + "winnow 0.7.15", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", ] [[package]] @@ -761,6 +804,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -895,6 +944,12 @@ dependencies = [ ] [[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" + +[[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -36,6 +36,7 @@ space-macros = { path = "rola-utils/space-system/macros" } rola-bucket = { path = "rola-bucket" } rola-draft = { path = "rola-draft" } +toml = "1.1.0" thiserror = "2.0.18" just_fmt = "0.1.2" log = "0.4.32" diff --git a/rola-bucket/Cargo.toml b/rola-bucket/Cargo.toml index 8899591..e5694dc 100644 --- a/rola-bucket/Cargo.toml +++ b/rola-bucket/Cargo.toml @@ -16,3 +16,4 @@ thiserror.workspace = true tokio.workspace = true serde.workspace = true log.workspace = true +toml.workspace = true diff --git a/rola-bucket/res/bucket.toml b/rola-bucket/res/bucket.toml deleted file mode 100644 index adc7770..0000000 --- a/rola-bucket/res/bucket.toml +++ /dev/null @@ -1,4 +0,0 @@ -[bucket] -# Bucket 类型 - type = "client" # 本地存储,使用本地 ID -# type = "bucket" # 中心存储,使用全局 ID diff --git a/rola-bucket/src/bucket/config.rs b/rola-bucket/src/bucket/config.rs index 559db15..2fd111c 100644 --- a/rola-bucket/src/bucket/config.rs +++ b/rola-bucket/src/bucket/config.rs @@ -1,9 +1,15 @@ -use serde::Deserialize; +use serde::{Deserialize, Serialize}; +use space_system::{Space, SpaceError}; + +use crate::{AsyncBucketTransferProtocol, Bucket}; + +#[cfg(test)] +mod test; /// Configuration for a bucket. /// /// This struct defines how a bucket should be configured, including its type. -#[derive(Default, Deserialize)] +#[derive(Default, Serialize, Deserialize)] pub struct BucketConfig { /// The type of the bucket, e.g., client bucket or normal bucket. /// @@ -15,7 +21,7 @@ pub struct BucketConfig { /// Enum for bucket types, used to distinguish different types of buckets. /// /// When deserializing, field names are mapped to string values in TOML via `serde(rename)`. -#[derive(Default, Deserialize)] +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] pub enum BucketType { /// Client bucket /// Uses local ID, mapped to remote ID via IDMAP @@ -28,3 +34,74 @@ pub enum BucketType { #[serde(rename = "bucket")] Bucket, } + +/// Reads the bucket configuration from a file path. +/// +/// This function reads the bucket configuration file at the given `path`. +/// If the file cannot be read or the content cannot be parsed as TOML, +/// a default `BucketConfig` will be used instead of returning an error. +/// +/// # Note +/// +/// - If the configuration file cannot be read (e.g., file not found, permission denied), +/// a default [`BucketConfig`] is returned. +/// - If the file content is not valid TOML or does not match the expected structure, +/// a default [`BucketConfig`] is returned as well. +pub fn read_bucket_config_from_path( + path: impl AsRef<std::path::Path>, +) -> Result<BucketConfig, SpaceError> { + let config_content = match std::fs::read_to_string(path) { + Ok(content) => content, + Err(_) => return Ok(BucketConfig::default()), + }; + Ok(toml::from_str(&config_content).unwrap_or_default()) +} + +/// Writes the bucket configuration to a file path. +/// +/// This function serializes the given bucket configuration as TOML and +/// writes it to the specified `path`. If the serialization or write fails, +/// the error is returned as a [`SpaceError`]. +pub fn write_bucket_config_to_path( + path: impl AsRef<std::path::Path>, + config: &BucketConfig, +) -> Result<(), SpaceError> { + let config_content = toml::to_string(config).map_err(|e| { + SpaceError::Other(format!("Failed to serialize bucket config to TOML: {}", e)) + })?; + std::fs::write(path, config_content.as_bytes()) + .map_err(|e| SpaceError::Other(format!("Failed to write bucket config to file: {}", e)))?; + Ok(()) +} + +/// Reads the bucket configuration from the space, with shared with_path logic. +/// +/// This function attempts to read the bucket configuration file (`FILE_BUCKET_ROOT_CONFIG`) +/// from the given `space`. If the file cannot be read or the content cannot be parsed as TOML, +/// a default `BucketConfig` will be used instead of returning an error. +/// +/// # Note +/// +/// - If the configuration file cannot be read (e.g., file not found, permission denied), +/// a default [`BucketConfig`] is returned. +/// - If the file content is not valid TOML or does not match the expected structure, +/// a default [`BucketConfig`] is returned as well. +pub fn read_bucket_config<Protocol: AsyncBucketTransferProtocol + Send + Sync>( + space: &Space<Bucket<Protocol>>, +) -> Result<BucketConfig, SpaceError> { + let path = space.local_path(shared_constants::common::FILE_BUCKET_ROOT_CONFIG)?; + read_bucket_config_from_path(path) +} + +/// Writes the bucket configuration to the space, with shared with_path logic. +/// +/// This function attempts to write the bucket configuration file (`FILE_BUCKET_ROOT_CONFIG`) +/// to the given `space` as a TOML string. If the serialization or write fails, the error +/// is returned. +pub fn write_bucket_config<Protocol: AsyncBucketTransferProtocol + Send + Sync>( + space: &Space<Bucket<Protocol>>, + config: &BucketConfig, +) -> Result<(), SpaceError> { + let path = space.local_path(shared_constants::common::FILE_BUCKET_ROOT_CONFIG)?; + write_bucket_config_to_path(path, config) +} diff --git a/rola-bucket/src/bucket/config/test.rs b/rola-bucket/src/bucket/config/test.rs new file mode 100644 index 0000000..d586045 --- /dev/null +++ b/rola-bucket/src/bucket/config/test.rs @@ -0,0 +1,116 @@ +use shared_functions::rola_test_sandbox; + +use crate::config::{ + BucketConfig, BucketType, read_bucket_config_from_path, write_bucket_config_to_path, +}; + +#[test] +fn test_read_bucket_config_from_path_file_not_found_returns_default() { + let sandbox = rola_test_sandbox("bucket_config_read_file_not_found"); + let path = sandbox.path.join("nonexistent.toml"); + + let config = read_bucket_config_from_path(&path).unwrap(); + assert_eq!(config.bucket_type, BucketType::Bucket); +} + +#[test] +fn test_read_bucket_config_from_path_invalid_toml_returns_default() { + let sandbox = rola_test_sandbox("bucket_config_read_invalid_toml"); + let path = sandbox.path.join("config.toml"); + std::fs::write(&path, "this is not valid TOML {{{").unwrap(); + + let config = read_bucket_config_from_path(&path).unwrap(); + assert_eq!(config.bucket_type, BucketType::Bucket); +} + +#[test] +fn test_read_bucket_config_from_path_valid_client_bucket() { + let sandbox = rola_test_sandbox("bucket_config_read_valid_client"); + let path = sandbox.path.join("config.toml"); + std::fs::write(&path, r#"type = "client""#).unwrap(); + + let config = read_bucket_config_from_path(&path).unwrap(); + assert_eq!(config.bucket_type, BucketType::ClientBucket); +} + +#[test] +fn test_read_bucket_config_from_path_valid_normal_bucket() { + let sandbox = rola_test_sandbox("bucket_config_read_valid_normal"); + let path = sandbox.path.join("config.toml"); + std::fs::write(&path, r#"type = "bucket""#).unwrap(); + + let config = read_bucket_config_from_path(&path).unwrap(); + assert_eq!(config.bucket_type, BucketType::Bucket); +} + +#[test] +fn test_read_bucket_config_from_path_empty_file_returns_default() { + let sandbox = rola_test_sandbox("bucket_config_read_empty"); + let path = sandbox.path.join("config.toml"); + std::fs::write(&path, "").unwrap(); + + let config = read_bucket_config_from_path(&path).unwrap(); + assert_eq!(config.bucket_type, BucketType::Bucket); +} + +#[test] +fn test_read_bucket_config_from_path_unknown_type_returns_default() { + let sandbox = rola_test_sandbox("bucket_config_read_unknown_type"); + let path = sandbox.path.join("config.toml"); + std::fs::write(&path, r#"type = "something_else""#).unwrap(); + + let config = read_bucket_config_from_path(&path).unwrap(); + assert_eq!(config.bucket_type, BucketType::Bucket); +} + +#[test] +fn test_write_and_read_roundtrip_client_bucket() { + let sandbox = rola_test_sandbox("bucket_config_roundtrip_client"); + let path = sandbox.path.join("config.toml"); + + let config = BucketConfig { + bucket_type: BucketType::ClientBucket, + }; + write_bucket_config_to_path(&path, &config).unwrap(); + + let loaded = read_bucket_config_from_path(&path).unwrap(); + assert_eq!(loaded.bucket_type, BucketType::ClientBucket); +} + +#[test] +fn test_write_and_read_roundtrip_normal_bucket() { + let sandbox = rola_test_sandbox("bucket_config_roundtrip_normal"); + let path = sandbox.path.join("config.toml"); + + let config = BucketConfig { + bucket_type: BucketType::Bucket, + }; + write_bucket_config_to_path(&path, &config).unwrap(); + + let loaded = read_bucket_config_from_path(&path).unwrap(); + assert_eq!(loaded.bucket_type, BucketType::Bucket); +} + +#[test] +fn test_write_bucket_config_to_path_invalid_dir_fails() { + let path = "/nonexistent_directory_12345/config.toml"; + + let config = BucketConfig::default(); + let result = write_bucket_config_to_path(&path, &config); + assert!(result.is_err()); +} + +#[test] +fn test_write_bucket_config_overwrites_existing_file() { + let sandbox = rola_test_sandbox("bucket_config_overwrite"); + let path = sandbox.path.join("config.toml"); + std::fs::write(&path, r#"type = "client""#).unwrap(); + + let config = BucketConfig { + bucket_type: BucketType::Bucket, + }; + write_bucket_config_to_path(&path, &config).unwrap(); + + let loaded = read_bucket_config_from_path(&path).unwrap(); + assert_eq!(loaded.bucket_type, BucketType::Bucket); +} diff --git a/rola-bucket/src/bucket/init.rs b/rola-bucket/src/bucket/init.rs index 6834009..d2dda24 100644 --- a/rola-bucket/src/bucket/init.rs +++ b/rola-bucket/src/bucket/init.rs @@ -12,6 +12,8 @@ use shared_constants::{ }; use space_system::SpaceError; +use crate::config::{BucketConfig, write_bucket_config_to_path}; + pub(crate) fn init_bucket_at(path: PathBuf) -> Result<(), SpaceError> { let bucket_config_file = path.join(FILE_BUCKET_ROOT_CONFIG); @@ -21,16 +23,12 @@ pub(crate) fn init_bucket_at(path: PathBuf) -> Result<(), SpaceError> { return Err(SpaceError::RequireEmptyDirectory); } - write_config(&bucket_config_file)?; + write_bucket_config_to_path(&bucket_config_file, &BucketConfig::default())?; create_dirs(&path)?; Ok(()) } -fn write_config(bucket_config_file: &Path) -> Result<(), SpaceError> { - fs::write(bucket_config_file, include_str!("../../res/bucket.toml")).map_err(SpaceError::Io) -} - fn create_dirs(bucket_dir: &Path) -> Result<(), SpaceError> { let dirs = [ DIR_BUCKET_OBJ, diff --git a/rola-bucket/src/bucket/space.rs b/rola-bucket/src/bucket/space.rs index 353075d..1775914 100644 --- a/rola-bucket/src/bucket/space.rs +++ b/rola-bucket/src/bucket/space.rs @@ -1,6 +1,4 @@ -// use log::trace; use shared_constants::common::FILE_BUCKET_ROOT_CONFIG; -use shared_functions::trace; use space_system::{SpaceRoot, SpaceRootFindPattern}; use crate::{AsyncBucketTransferProtocol, Bucket, bucket::init::init_bucket_at}; @@ -11,10 +9,7 @@ impl<Protocol: AsyncBucketTransferProtocol + Send + Sync> SpaceRoot for Bucket<P } fn create_space(path: &std::path::Path) -> Result<(), space_system::SpaceError> { - let path_str = path.display().to_string(); - trace!("Creating bucket at: {}", &path_str); init_bucket_at(path.into())?; - trace!("Bucket created at: {}", &path_str); Ok(()) } } |
