lib.rs (5210B)
1 #![forbid(unsafe_code)] 2 3 pub mod backup; 4 pub(crate) mod client_message; 5 pub mod config; 6 pub mod errors; 7 pub mod event_bus; 8 pub mod export; 9 pub mod groups; 10 pub mod host; 11 pub mod logging; 12 pub mod nip11; 13 pub mod ops; 14 pub(crate) mod pocket_conversion; 15 pub(crate) mod pocket_event_validation; 16 pub mod portable_backup; 17 pub mod rate_limits; 18 pub mod relay; 19 pub mod resource_limits; 20 pub mod runtime; 21 pub mod server; 22 pub mod session; 23 pub mod tenant; 24 25 use std::{fmt, fs, path::Path, path::PathBuf}; 26 27 use config::{ 28 TangleHostRuntimeConfigSet, parse_tangle_host_runtime_config_json, 29 parse_tenant_runtime_config_json, 30 }; 31 use errors::BaseRelayError; 32 33 pub const TANGLE_RELAY_SOFTWARE: &str = "https://github.com/radrootslabs/tangle"; 34 pub const TANGLE_RELAY_VERSION: &str = env!("CARGO_PKG_VERSION"); 35 36 #[derive(Debug)] 37 pub enum TangleRuntimeLoadError { 38 ReadConfig { 39 path: PathBuf, 40 source: std::io::Error, 41 }, 42 ReadTenantConfigDir { 43 path: PathBuf, 44 source: std::io::Error, 45 }, 46 ReadTenantConfig { 47 path: PathBuf, 48 source: std::io::Error, 49 }, 50 ParseConfig(BaseRelayError), 51 OpenRelay(BaseRelayError), 52 } 53 54 impl fmt::Display for TangleRuntimeLoadError { 55 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 56 match self { 57 Self::ReadConfig { path, source } => { 58 write!( 59 formatter, 60 "failed to read tangle runtime config `{}`: {source}", 61 path.display() 62 ) 63 } 64 Self::ReadTenantConfigDir { path, source } => { 65 write!( 66 formatter, 67 "failed to read tangle tenant config directory `{}`: {source}", 68 path.display() 69 ) 70 } 71 Self::ReadTenantConfig { path, source } => { 72 write!( 73 formatter, 74 "failed to read tangle tenant config `{}`: {source}", 75 path.display() 76 ) 77 } 78 Self::ParseConfig(error) => write!(formatter, "{error}"), 79 Self::OpenRelay(error) => write!(formatter, "{error}"), 80 } 81 } 82 } 83 84 impl std::error::Error for TangleRuntimeLoadError {} 85 86 pub fn load_tangle_host_runtime_config( 87 path: impl AsRef<Path>, 88 ) -> Result<TangleHostRuntimeConfigSet, TangleRuntimeLoadError> { 89 let path = path.as_ref(); 90 let raw = fs::read_to_string(path).map_err(|source| TangleRuntimeLoadError::ReadConfig { 91 path: path.to_path_buf(), 92 source, 93 })?; 94 let host = 95 parse_tangle_host_runtime_config_json(&raw).map_err(TangleRuntimeLoadError::ParseConfig)?; 96 let config_dir = resolve_config_path(path.parent(), host.tenant_config_dir()); 97 let mut tenant_paths = fs::read_dir(&config_dir) 98 .map_err(|source| TangleRuntimeLoadError::ReadTenantConfigDir { 99 path: config_dir.clone(), 100 source, 101 })? 102 .map(|entry| entry.map(|entry| entry.path())) 103 .collect::<Result<Vec<_>, _>>() 104 .map_err(|source| TangleRuntimeLoadError::ReadTenantConfigDir { 105 path: config_dir.clone(), 106 source, 107 })?; 108 tenant_paths.retain(|path| { 109 path.is_file() 110 && path 111 .extension() 112 .is_some_and(|extension| extension == "json") 113 }); 114 tenant_paths.sort(); 115 let mut tenants = Vec::with_capacity(tenant_paths.len()); 116 for tenant_path in tenant_paths { 117 let raw = fs::read_to_string(&tenant_path).map_err(|source| { 118 TangleRuntimeLoadError::ReadTenantConfig { 119 path: tenant_path.clone(), 120 source, 121 } 122 })?; 123 let tenant = 124 parse_tenant_runtime_config_json(&raw).map_err(TangleRuntimeLoadError::ParseConfig)?; 125 tenants.push(tenant); 126 } 127 TangleHostRuntimeConfigSet::new(host, tenants).map_err(TangleRuntimeLoadError::ParseConfig) 128 } 129 130 fn resolve_config_path(base: Option<&Path>, path: &Path) -> PathBuf { 131 if path.is_absolute() { 132 path.to_path_buf() 133 } else if let Some(base) = base { 134 base.join(path) 135 } else { 136 path.to_path_buf() 137 } 138 } 139 140 #[cfg(test)] 141 mod tests { 142 use crate::pocket_conversion::{pocket_event_to_tangle, tangle_event_to_pocket}; 143 use tangle_protocol::{Tag, event_from_value, event_to_value}; 144 use tangle_test_support::{FixtureKey, tangle_v2_event}; 145 146 #[test] 147 fn pocket_event_conversion_accepts_protocol_event_json_shapes() { 148 let event = tangle_v2_event( 149 FixtureKey::Owner, 150 1_714_124_433, 151 30_402, 152 vec![ 153 Tag::from_parts("d", &["market"]).expect("d"), 154 Tag::from_parts("t", &["radroots", "farm"]).expect("t"), 155 ], 156 "json parity", 157 ) 158 .expect("event"); 159 let parsed = event_from_value(&event_to_value(&event)).expect("parsed"); 160 let pocket = tangle_event_to_pocket(&parsed).expect("pocket"); 161 let converted = pocket_event_to_tangle(&pocket).expect("converted"); 162 163 assert_eq!(parsed, event); 164 assert_eq!(converted, event); 165 } 166 }