config.rs (11506B)
1 use std::io::Write; 2 3 use kdl::{KdlDocument, KdlEntry, KdlIdentifier, KdlNode}; 4 use miette::{Diagnostic, NamedSource, SourceSpan}; 5 use thiserror::Error; 6 7 pub struct Config { 8 pub homeserver_url: String, 9 pub indradb_endpoint: String, 10 pub auth_data: AuthData, 11 } 12 13 pub enum AuthData { 14 UsernamePassword(String, String), 15 AccessToken(String, String, String), 16 } 17 18 #[derive(Debug, Error, Diagnostic)] 19 #[error("Incorrect Config")] 20 #[diagnostic()] 21 pub struct MissingFieldError { 22 #[source_code] 23 src: NamedSource, 24 25 #[help] 26 advice: String, 27 28 #[label] 29 snippet: SourceSpan, 30 } 31 32 fn parse(input: &str) -> miette::Result<KdlDocument> { 33 Ok(input.parse::<KdlDocument>()?) 34 } 35 36 fn missing_field(source: String, snippet: &SourceSpan, help: &str) { 37 let error: miette::Result<()> = Err(MissingFieldError { 38 src: NamedSource::new("config.kdl", source), 39 snippet: *snippet, 40 advice: help.to_string(), 41 } 42 .into()); 43 44 // This is stupid. But oh well. 45 if let Err(error) = error { 46 eprintln!("{error:?}"); 47 std::process::exit(1); 48 } 49 } 50 51 #[allow(clippy::unwrap_used)] 52 #[allow(clippy::too_many_lines)] 53 pub fn load() -> Config { 54 let source = std::fs::read_to_string("config.kdl") 55 .expect("Unable to open config.kdl file. Is it present?"); 56 match parse(&source) { 57 Ok(config) => { 58 let matrix = config.get("matrix"); 59 if let Some(matrix) = matrix { 60 if let Some(matrix_children) = matrix.children() { 61 let homeserver_url_entry = matrix_children.get("homeserver_url"); 62 let homeserver_url = if let Some(homeserver_url_entry) = homeserver_url_entry { 63 if let Some(homeserver_url) = homeserver_url_entry.entries().first() { 64 if let Some(homeserver_url) = homeserver_url.value().as_string() { 65 homeserver_url.to_string() 66 } else { 67 missing_field(source, homeserver_url.span(),"\"homeserver_url\" field has incorrect type. Make sure it is actually a string. Try adding quotes."); 68 unreachable!(); 69 } 70 } else { 71 missing_field(source, homeserver_url_entry.span(),"\"homeserver_url\" field has no value. Make sure it is actually a string. Try adding quotes."); 72 unreachable!(); 73 } 74 } else { 75 missing_field(source,matrix_children.span(),"Missing \"homeserver_url\" field. See the example config for how to define it"); 76 unreachable!(); 77 }; 78 let username_entry = matrix_children.get("username"); 79 let username = if let Some(username_entry) = username_entry { 80 if let Some(username) = username_entry.entries().first() { 81 if let Some(username) = username.value().as_string() { 82 username.to_string() 83 } else { 84 missing_field(source, username.span(),"\"username\" field has incorrect type. Make sure it is actually a string. Try adding quotes."); 85 unreachable!(); 86 } 87 } else { 88 missing_field(source, username_entry.span(),"\"username\" field has no value. Make sure it is actually a string. Try adding quotes."); 89 unreachable!(); 90 } 91 } else { 92 missing_field(source,matrix_children.span(),"Missing \"username\" field. See the example config for how to define it"); 93 unreachable!(); 94 }; 95 let password_entry = matrix_children.get("password"); 96 let password = if let Some(password_entry) = password_entry { 97 if let Some(password) = password_entry.entries().first() { 98 if let Some(password) = password.value().as_string() { 99 Some(password.to_string()) 100 } else { 101 missing_field(source, password.span(),"\"password\" field has incorrect type. Make sure it is actually a string. Try adding quotes."); 102 unreachable!(); 103 } 104 } else { 105 missing_field(source, password_entry.span(),"\"password\" field has no value. Make sure it is actually a string. Try adding quotes."); 106 unreachable!(); 107 } 108 } else { 109 None 110 }; 111 112 let indradb_address_entry = config.get("indradb_address"); 113 let indradb_endpoint = if let Some(indradb_address_entry) = 114 indradb_address_entry 115 { 116 if let Some(indradb_address) = indradb_address_entry.entries().first() { 117 if let Some(indradb_address) = indradb_address.value().as_string() { 118 indradb_address.to_string() 119 } else { 120 missing_field(source, indradb_address.span(),"\"indradb_address\" field has incorrect type. Make sure it is actually a string. Try adding quotes."); 121 unreachable!(); 122 } 123 } else { 124 missing_field(source, indradb_address_entry.span(),"\"indradb_address\" field has no value. Make sure it is actually a string. Try adding quotes."); 125 unreachable!(); 126 } 127 } else { 128 missing_field(source,config.span(),"Missing \"indradb_address\" field. See the example config for how to define it"); 129 unreachable!(); 130 }; 131 132 let access_token_entry = matrix_children.get("access_token"); 133 let access_token = if let Some(access_token_entry) = access_token_entry { 134 if let Some(access_token) = access_token_entry.entries().first() { 135 if let Some(access_token) = access_token.value().as_string() { 136 Some(access_token.to_string()) 137 } else { 138 missing_field(source, access_token.span(),"\"access_token\" field has incorrect type. Please reset it to be username and password."); 139 unreachable!(); 140 } 141 } else { 142 missing_field(source, access_token_entry.span(),"\"access_token\" field has no value. Please reset it to be username and password."); 143 unreachable!(); 144 } 145 } else { 146 None 147 }; 148 let device_id_entry = matrix_children.get("device_id"); 149 let device_id = if let Some(device_id_entry) = device_id_entry { 150 if let Some(device_id) = device_id_entry.entries().first() { 151 if let Some(device_id) = device_id.value().as_string() { 152 Some(device_id.to_string()) 153 } else { 154 missing_field(source, device_id.span(),"\"device_id\" field has incorrect type. Please reset it to be username and password."); 155 unreachable!(); 156 } 157 } else { 158 missing_field(source, device_id_entry.span(),"\"device_id\" field has no value. Please reset it to be username and password."); 159 unreachable!(); 160 } 161 } else { 162 None 163 }; 164 165 let auth_data = if let Some(password) = password { 166 AuthData::UsernamePassword(username, password) 167 } else { 168 // In theory this is safe :D 169 AuthData::AccessToken(username, access_token.unwrap(), device_id.unwrap()) 170 }; 171 172 Config { 173 homeserver_url, 174 indradb_endpoint, 175 auth_data, 176 } 177 } else { 178 missing_field( 179 source, 180 matrix.span(), 181 "Missing \"matrix\" choldren. See the example config for how to define it", 182 ); 183 unreachable!(); 184 } 185 } else { 186 missing_field( 187 source, 188 config.span(), 189 "Missing \"matrix\" section. See the example config for how to define it", 190 ); 191 unreachable!(); 192 } 193 } 194 Err(error) => { 195 eprintln!("{error:?}"); 196 std::process::exit(1); 197 } 198 } 199 } 200 201 pub fn write_access_token(access_token: String, device_id: String) -> color_eyre::Result<()> { 202 let source = std::fs::read_to_string("config.kdl") 203 .expect("Unable to open config.kdl file. Is it present?"); 204 let config = parse(&source); 205 match config { 206 Ok(mut config) => { 207 let matrix = config.get_mut("matrix"); 208 if let Some(matrix) = matrix { 209 if let Some(matrix_children) = matrix.children_mut() { 210 let matrix_nodes = matrix_children.nodes_mut(); 211 let access_token_identifier = KdlIdentifier::from("access_token"); 212 let device_id_identifier = KdlIdentifier::from("device_id"); 213 214 let mut access_token_node = KdlNode::new(access_token_identifier); 215 let access_token_entry = KdlEntry::new(access_token); 216 access_token_node.entries_mut().push(access_token_entry); 217 218 let mut device_id_node = KdlNode::new(device_id_identifier); 219 let device_id_entry = KdlEntry::new(device_id); 220 device_id_node.entries_mut().push(device_id_entry); 221 222 matrix_nodes.push(access_token_node); 223 matrix_nodes.push(device_id_node); 224 matrix_nodes.retain(|node| node.name().value() != "password"); 225 226 matrix_nodes.sort_by(sort_by_name); 227 } 228 matrix.fmt(); 229 } 230 231 let mut file = std::fs::OpenOptions::new() 232 .write(true) 233 .truncate(true) 234 .open("config.kdl")?; 235 236 file.write_all(config.to_string().as_bytes())?; 237 file.flush()?; 238 Ok(()) 239 } 240 Err(error) => { 241 let err = format!("{error:?}"); 242 Err(color_eyre::eyre::eyre!("{}", err)) 243 } 244 } 245 } 246 247 fn sort_by_name(x: &KdlNode, y: &KdlNode) -> std::cmp::Ordering { 248 x.name().value().cmp(y.name().value()) 249 }