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