diff --git a/Cargo.lock b/Cargo.lock index 2b3b291..87abc25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3985,7 +3985,9 @@ dependencies = [ "objects-transport", "serde", "serde_json", + "thiserror 2.0.17", "tokio", + "toml", "tracing", "tracing-subscriber", ] @@ -4522,7 +4524,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit", + "toml_edit 0.23.10+spec-1.0.0", ] [[package]] @@ -5446,6 +5448,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_test" version = "1.0.177" @@ -6313,6 +6324,27 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + [[package]] name = "toml_datetime" version = "0.7.5+spec-1.1.0" @@ -6322,6 +6354,20 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + [[package]] name = "toml_edit" version = "0.23.10+spec-1.0.0" @@ -6329,7 +6375,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "winnow", ] @@ -6343,6 +6389,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tonic" version = "0.14.2" diff --git a/bins/objects-node/Cargo.toml b/bins/objects-node/Cargo.toml index 32a2f00..30d2db8 100644 --- a/bins/objects-node/Cargo.toml +++ b/bins/objects-node/Cargo.toml @@ -19,9 +19,11 @@ futures.workspace = true # Serialization serde.workspace = true serde_json.workspace = true +toml = "0.8" # Error handling anyhow.workspace = true +thiserror.workspace = true # Logging tracing.workspace = true diff --git a/bins/objects-node/src/config.rs b/bins/objects-node/src/config.rs new file mode 100644 index 0000000..33afd42 --- /dev/null +++ b/bins/objects-node/src/config.rs @@ -0,0 +1,156 @@ +//! Configuration types for the OBJECTS node daemon. + +use serde::{Deserialize, Serialize}; + +/// Main configuration for the OBJECTS node daemon. +/// +/// Configuration is loaded from a TOML file and can be overridden by environment variables. +/// See individual field documentation for environment variable names. +/// +/// # Example +/// +/// ``` +/// use objects_node::config::NodeConfig; +/// +/// let config = NodeConfig::default(); +/// assert_eq!(config.node.api_port, 3420); +/// assert_eq!(config.network.relay_url, "https://relay.objects.foundation"); +/// ``` +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct NodeConfig { + /// Node-specific settings (data directory, API configuration). + pub node: NodeSettings, + /// Network settings (relay, discovery). + pub network: NetworkSettings, + /// Storage limits and constraints. + pub storage: StorageSettings, + /// Identity and registry settings. + pub identity: IdentitySettings, +} + +/// Node-specific configuration settings. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NodeSettings { + /// Directory for node data storage (config, state, blobs). + /// + /// Environment variable: `OBJECTS_DATA_DIR` + pub data_dir: String, + /// Port for the node API server. + /// + /// Environment variable: `OBJECTS_API_PORT` + pub api_port: u16, + /// IP address to bind the API server to. + pub api_bind: String, +} + +impl Default for NodeSettings { + fn default() -> Self { + Self { + data_dir: "~/.objects".to_string(), + api_port: 3420, + api_bind: "127.0.0.1".to_string(), + } + } +} + +/// Network configuration settings. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NetworkSettings { + /// URL of the relay server for NAT traversal. + /// + /// Environment variable: `OBJECTS_RELAY_URL` + pub relay_url: String, + /// Discovery topic for peer discovery. + /// + /// Format: `/objects/{network}/0.1/discovery` where network is `devnet` or `mainnet`. + pub discovery_topic: String, +} + +impl Default for NetworkSettings { + fn default() -> Self { + Self { + relay_url: "https://relay.objects.foundation".to_string(), + discovery_topic: "/objects/devnet/0.1/discovery".to_string(), + } + } +} + +/// Storage configuration settings. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StorageSettings { + /// Maximum size for a single blob in megabytes. + pub max_blob_size_mb: u64, + /// Maximum total storage size in gigabytes. + pub max_total_size_gb: u64, +} + +impl Default for StorageSettings { + fn default() -> Self { + Self { + max_blob_size_mb: 100, + max_total_size_gb: 10, + } + } +} + +/// Identity and registry configuration settings. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct IdentitySettings { + /// URL of the OBJECTS registry service. + /// + /// Environment variable: `OBJECTS_REGISTRY_URL` + pub registry_url: String, +} + +impl Default for IdentitySettings { + fn default() -> Self { + Self { + registry_url: "https://registry.objects.foundation".to_string(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_default_config_serialization() { + let config = NodeConfig::default(); + + // Serialize to JSON + let json = serde_json::to_string_pretty(&config).unwrap(); + + // Deserialize back + let deserialized: NodeConfig = serde_json::from_str(&json).unwrap(); + + // Verify round-trip + assert_eq!(config.node.api_port, deserialized.node.api_port); + assert_eq!(config.node.data_dir, deserialized.node.data_dir); + assert_eq!(config.network.relay_url, deserialized.network.relay_url); + assert_eq!( + config.network.discovery_topic, + deserialized.network.discovery_topic + ); + } + + #[test] + fn test_default_values() { + let config = NodeConfig::default(); + + assert_eq!(config.node.data_dir, "~/.objects"); + assert_eq!(config.node.api_port, 3420); + assert_eq!(config.node.api_bind, "127.0.0.1"); + assert_eq!(config.network.relay_url, "https://relay.objects.foundation"); + assert_eq!( + config.network.discovery_topic, + "/objects/devnet/0.1/discovery" + ); + assert_eq!( + config.identity.registry_url, + "https://registry.objects.foundation" + ); + assert_eq!(config.storage.max_blob_size_mb, 100); + assert_eq!(config.storage.max_total_size_gb, 10); + } +} diff --git a/bins/objects-node/src/main.rs b/bins/objects-node/src/main.rs index 7c9f490..2d186ba 100644 --- a/bins/objects-node/src/main.rs +++ b/bins/objects-node/src/main.rs @@ -1,5 +1,7 @@ //! Node daemon for OBJECTS Protocol. +pub mod config; + use tracing::info; #[tokio::main]