summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-06-19 23:42:19 +0800
committer魏曹先生 <1992414357@qq.com>2026-06-19 23:42:19 +0800
commit40183e431da97fe0377b2f2e2ea47b3a14376680 (patch)
tree0c117e2ec707f3ad33206f1e320af2b439f80dbe
parentd36acf196f7475a91550cc64c9c9a545adb65c50 (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.lock67
-rw-r--r--Cargo.toml1
-rw-r--r--rola-bucket/Cargo.toml1
-rw-r--r--rola-bucket/res/bucket.toml4
-rw-r--r--rola-bucket/src/bucket/config.rs83
-rw-r--r--rola-bucket/src/bucket/config/test.rs116
-rw-r--r--rola-bucket/src/bucket/init.rs8
-rw-r--r--rola-bucket/src/bucket/space.rs5
8 files changed, 262 insertions, 23 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 23750b8..564e88f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 79be524..dac5e67 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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(())
}
}