1use gethostname::gethostname;
2use roxmltree;
3use std::fmt;
4use std::fs;
5use std::str::FromStr;
6use std::sync::OnceLock;
7use syslog;
8
9static GLOBAL_OSRF_CONFIG: OnceLock<Config> = OnceLock::new();
10
11pub fn config() -> &'static Config {
15 if let Some(conf) = GLOBAL_OSRF_CONFIG.get() {
16 conf
17 } else {
18 log::error!("OpenSRF Config Required");
19 panic!("OpenSRF Config Required")
20 }
21}
22
23const DEFAULT_BUS_PORT: u16 = 6379;
24
25#[derive(Debug, Clone, PartialEq)]
26pub enum LogFile {
27 Syslog,
28 Stdout,
29 Filename(String),
30}
31
32#[derive(Debug, Clone)]
33pub struct LogOptions {
34 log_level: Option<log::LevelFilter>,
35 log_file: Option<LogFile>,
36 syslog_facility: Option<syslog::Facility>,
37 activity_log_facility: Option<syslog::Facility>,
38}
39
40impl LogOptions {
41 pub fn syslog_facility(&self) -> Option<syslog::Facility> {
42 self.syslog_facility
43 }
44 pub fn set_syslog_facility(&mut self, facility: &str) -> Result<(), String> {
45 if let Ok(ff) = syslog::Facility::from_str(facility) {
46 self.syslog_facility = Some(ff);
47 Ok(())
48 } else {
49 Err(format!("Invalid syslog facility string: {facility}"))
50 }
51 }
52
53 pub fn activity_log_facility(&self) -> Option<syslog::Facility> {
54 self.activity_log_facility
55 }
56 pub fn log_file(&self) -> &Option<LogFile> {
57 &self.log_file
58 }
59 pub fn set_log_file(&mut self, file: LogFile) {
60 self.log_file = Some(file);
61 }
62 pub fn log_level(&self) -> &Option<log::LevelFilter> {
63 &self.log_level
64 }
65 pub fn set_log_level(&mut self, level: &str) {
66 self.log_level = Some(LogOptions::log_level_from_str(level));
67 }
68
69 pub fn log_level_from_str(level: &str) -> log::LevelFilter {
74 match level {
75 "1" | "error" => log::LevelFilter::Error,
76 "2" | "warn" => log::LevelFilter::Warn,
77 "3" | "info" => log::LevelFilter::Info,
78 "4" | "debug" => log::LevelFilter::Debug,
79 "5" | "trace" => log::LevelFilter::Trace,
80 _ => log::LevelFilter::Info,
81 }
82 }
83}
84
85#[derive(Debug, Clone)]
87pub struct BusDomain {
88 name: String,
89 port: u16,
90}
91
92impl BusDomain {
93 pub fn name(&self) -> &str {
94 &self.name
95 }
96 pub fn port(&self) -> u16 {
97 self.port
98 }
99}
100
101impl fmt::Display for BusDomain {
102 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
103 write!(f, "{}:{}", self.name, self.port)
104 }
105}
106
107#[derive(Debug, Clone)]
109pub struct BusClient {
110 username: String,
111 password: String,
112 router_name: String,
113 domain: BusDomain,
114 logging: LogOptions,
115 settings_config: Option<String>,
116 routers: Vec<ClientRouter>,
117}
118
119impl BusClient {
120 pub fn username(&self) -> &str {
121 &self.username
122 }
123 pub fn password(&self) -> &str {
124 &self.password
125 }
126 pub fn domain(&self) -> &BusDomain {
127 &self.domain
128 }
129 pub fn router_name(&self) -> &str {
131 &self.router_name
132 }
133 pub fn logging(&self) -> &LogOptions {
134 &self.logging
135 }
136 pub fn logging_mut(&mut self) -> &mut LogOptions {
137 &mut self.logging
138 }
139 pub fn settings_config(&self) -> Option<&str> {
140 self.settings_config.as_deref()
141 }
142 pub fn routers(&self) -> &Vec<ClientRouter> {
143 &self.routers
144 }
145 pub fn set_domain(&mut self, domain: &str) {
146 self.domain.name = domain.to_string();
148 }
149 pub fn set_username(&mut self, username: &str) {
150 self.username = username.to_string();
151 }
152 pub fn set_password(&mut self, password: &str) {
153 self.password = password.to_string();
154 }
155}
156
157#[derive(Debug, Clone)]
158pub struct ClientRouter {
159 domain: String,
160 username: String,
161 services: Option<Vec<String>>,
162}
163impl ClientRouter {
164 pub fn services(&self) -> Option<&Vec<String>> {
165 self.services.as_ref()
166 }
167 pub fn domain(&self) -> &str {
168 &self.domain
169 }
170 pub fn username(&self) -> &str {
172 &self.username
173 }
174}
175
176#[derive(Debug, Clone)]
177pub struct Router {
178 client: BusClient,
179 trusted_server_domains: Vec<String>,
180 trusted_client_domains: Vec<String>,
181}
182
183impl Router {
184 pub fn client(&self) -> &BusClient {
185 &self.client
186 }
187 pub fn client_mut(&mut self) -> &mut BusClient {
188 &mut self.client
189 }
190 pub fn trusted_server_domains(&self) -> &Vec<String> {
191 &self.trusted_server_domains
192 }
193 pub fn trusted_client_domains(&self) -> &Vec<String> {
194 &self.trusted_client_domains
195 }
196}
197
198#[derive(Debug, Clone)]
199pub struct ConfigBuilder {
200 client: Option<BusClient>,
201 routers: Vec<Router>,
202 gateway: Option<BusClient>,
203 log_protect: Vec<String>,
204}
205
206impl ConfigBuilder {
207 pub fn build(self) -> Result<Config, String> {
208 if self.client.is_none() {
209 return Err("Config has no client settings".to_string());
210 }
211
212 Ok(Config {
213 hostname: Config::get_os_hostname()?,
214 client: self.client.unwrap(),
215 routers: self.routers,
216 gateway: self.gateway,
217 log_protect: self.log_protect,
218 })
219 }
220
221 pub fn from_file(filename: &str) -> Result<Self, String> {
226 match fs::read_to_string(filename) {
227 Ok(text) => ConfigBuilder::from_xml_string(&text),
228 Err(e) => Err(format!(
229 "Error reading configuration file: file='{}' {:?}",
230 filename, e
231 )),
232 }
233 }
234
235 pub fn from_xml_string(xml: &str) -> Result<Self, String> {
236 let doc = roxmltree::Document::parse(xml).map_err(|e| format!("Error parsing XML: {e}"))?;
237
238 let conf_node = match doc.root().children().find(|n| n.has_tag_name("config")) {
239 Some(n) => n,
240 None => Err("Missing 'config' element".to_string())?,
241 };
242
243 let mut builder = ConfigBuilder {
244 client: None,
245 gateway: None,
246 routers: Vec::new(),
247 log_protect: Vec::new(),
248 };
249
250 for node in conf_node.children() {
253 match node.tag_name().name() {
254 "opensrf" => builder.unpack_opensrf_node(&node)?,
255 "routers" => builder.unpack_routers(&node)?,
256 "gateway" => builder.unpack_gateway(&node)?,
257 "shared" => builder.unpack_shared(&node)?,
258 _ => {} }
260 }
261
262 Ok(builder)
263 }
264
265 fn unpack_gateway(&mut self, node: &roxmltree::Node) -> Result<(), String> {
266 self.gateway = Some(self.unpack_client_node(node)?);
267 Ok(())
268 }
269
270 fn unpack_shared(&mut self, node: &roxmltree::Node) -> Result<(), String> {
271 if let Some(lp) = node.children().find(|c| c.has_tag_name("log_protect")) {
272 for ms in lp.children().filter(|c| c.has_tag_name("match_string")) {
273 if let Some(t) = ms.text() {
274 self.log_protect.push(t.to_string());
275 }
276 }
277 }
278
279 Ok(())
280 }
281
282 fn unpack_routers(&mut self, node: &roxmltree::Node) -> Result<(), String> {
283 for rnode in node.children().filter(|n| n.has_tag_name("router")) {
284 let tnode = match rnode.children().find(|c| c.has_tag_name("transport")) {
286 Some(tn) => tn,
287 None => Err("Routers require a transport config".to_string())?,
288 };
289
290 let mut client = self.unpack_client_node(&tnode)?;
291
292 client.logging = self.unpack_logging_node(&rnode)?;
295
296 let mut router = Router {
297 client,
298 trusted_server_domains: Vec::new(),
299 trusted_client_domains: Vec::new(),
300 };
301
302 for tdnode in rnode
303 .children()
304 .filter(|d| d.has_tag_name("trusted_domains"))
305 {
306 for snode in tdnode.children().filter(|d| d.has_tag_name("server")) {
307 if let Some(domain) = snode.text() {
308 router.trusted_server_domains.push(domain.to_string());
309 }
310 }
311 for cnode in tdnode.children().filter(|d| d.has_tag_name("client")) {
312 if let Some(domain) = cnode.text() {
313 router.trusted_client_domains.push(domain.to_string());
314 }
315 }
316 }
317
318 self.routers.push(router);
319 }
320
321 Ok(())
322 }
323
324 fn child_node_text(&self, node: &roxmltree::Node, name: &str) -> Option<String> {
325 if let Some(tnode) = node.children().find(|n| n.has_tag_name(name)) {
326 if let Some(text) = tnode.text() {
327 return Some(text.to_string());
328 }
329 }
330 None
331 }
332
333 fn unpack_opensrf_node(&mut self, node: &roxmltree::Node) -> Result<(), String> {
334 let mut client = self.unpack_client_node(node)?;
335
336 if let Some(routers) = node.children().find(|c| c.has_tag_name("routers")) {
337 for rnode in routers.children().filter(|r| r.has_tag_name("router")) {
338 self.unpack_client_router_node(&mut client, &rnode)?;
339 }
340 }
341
342 self.client = Some(client);
343
344 Ok(())
345 }
346
347 fn unpack_client_router_node(
348 &mut self,
349 client: &mut BusClient,
350 rnode: &roxmltree::Node,
351 ) -> Result<(), String> {
352 let domain = match self.child_node_text(rnode, "domain") {
353 Some(d) => d.to_string(),
354 None => Err(format!("Client router node has no domain: {rnode:?}"))?,
355 };
356
357 let username = match self.child_node_text(rnode, "name") {
358 Some(d) => d.to_string(),
359 None => Err(format!("Client router node has no name: {rnode:?}"))?,
360 };
361
362 let mut cr = ClientRouter {
363 domain,
364 username,
365 services: None,
366 };
367
368 if let Some(services) = rnode.children().find(|n| n.has_tag_name("services")) {
369 let mut svclist = Vec::new();
370
371 for snode in services.children().filter(|n| n.has_tag_name("service")) {
372 if let Some(service) = snode.text() {
373 svclist.push(service.to_string());
374 }
375 }
376
377 cr.services = Some(svclist);
378 }
379
380 client.routers.push(cr);
381
382 Ok(())
383 }
384
385 fn unpack_client_node(&mut self, node: &roxmltree::Node) -> Result<BusClient, String> {
386 let logging = self.unpack_logging_node(node)?;
387 let domain = self.unpack_domain_node(node)?;
388
389 let mut username = "";
390 let mut password = "";
391 let mut router_name = "router";
392 let mut settings_config: Option<String> = None;
393
394 for child in node.children() {
395 match child.tag_name().name() {
396 "username" => {
397 if let Some(t) = child.text() {
398 username = t;
399 }
400 }
401 "passwd" | "password" => {
402 if let Some(t) = child.text() {
403 password = t;
404 }
405 }
406 "router_name" => {
407 if let Some(t) = child.text() {
408 router_name = t;
409 }
410 }
411 "settings_config" => {
412 if let Some(t) = child.text() {
413 settings_config = Some(t.to_string());
414 }
415 }
416 _ => {}
417 }
418 }
419
420 Ok(BusClient {
421 domain,
422 logging,
423 settings_config,
424 routers: Vec::new(),
425 username: username.to_string(),
426 password: password.to_string(),
427 router_name: router_name.to_string(),
428 })
429 }
430
431 fn unpack_domain_node(&mut self, node: &roxmltree::Node) -> Result<BusDomain, String> {
432 let domain_name = match node.children().find(|c| c.has_tag_name("domain")) {
433 Some(n) => match n.text() {
434 Some(t) => t,
435 None => return Err("'domain' node is empty".to_string()),
436 },
437 None => match node.children().find(|c| c.has_tag_name("server")) {
438 Some(n) => match n.text() {
439 Some(t) => t,
440 None => return Err("'server' node is empty".to_string()),
441 },
442 None => return Err("Node has no domain or server".to_string()),
443 },
444 };
445
446 let mut port = DEFAULT_BUS_PORT;
447 if let Some(pnode) = node.children().find(|c| c.has_tag_name("port")) {
448 if let Some(ptext) = pnode.text() {
449 if let Ok(p) = ptext.parse::<u16>() {
450 port = p;
451 }
452 }
453 }
454
455 Ok(BusDomain {
456 port,
457 name: domain_name.to_string(),
458 })
459 }
460
461 fn unpack_logging_node(&mut self, node: &roxmltree::Node) -> Result<LogOptions, String> {
462 let mut ops = LogOptions {
463 log_level: None,
464 log_file: None,
465 syslog_facility: None,
466 activity_log_facility: None,
467 };
468
469 for child in node.children() {
470 match child.tag_name().name() {
471 "logfile" => {
472 if let Some(filename) = child.text() {
473 if filename.eq("syslog") {
474 ops.log_file = Some(LogFile::Syslog);
475 } else if filename.eq("stdout") {
476 ops.log_file = Some(LogFile::Stdout);
477 } else {
478 ops.log_file = Some(LogFile::Filename(filename.to_string()))
479 }
480 }
481 }
482 "syslog" => {
483 if let Some(f) = child.text() {
484 if let Ok(ff) = syslog::Facility::from_str(f) {
485 ops.syslog_facility = Some(ff);
486 }
487 }
488 }
489 "actlog" => {
490 if let Some(f) = child.text() {
491 if let Ok(ff) = syslog::Facility::from_str(f) {
492 ops.activity_log_facility = Some(ff);
493 }
494 }
495 }
496 "loglevel" => {
497 if let Some(level_num) = child.text() {
498 ops.log_level = Some(LogOptions::log_level_from_str(level_num));
499 }
500 }
501 _ => {}
502 }
503 }
504
505 Ok(ops)
506 }
507}
508
509#[derive(Debug, Clone)]
510pub struct Config {
511 hostname: String,
512 client: BusClient,
513 routers: Vec<Router>,
514 gateway: Option<BusClient>,
515 log_protect: Vec<String>,
516}
517
518impl Config {
519 pub fn store(self) -> Result<(), String> {
523 if GLOBAL_OSRF_CONFIG.set(self).is_err() {
524 Err("Cannot initialize OpenSRF Config more than once".to_string())
525 } else {
526 Ok(())
527 }
528 }
529
530 pub fn routers(&self) -> &Vec<Router> {
531 &self.routers
532 }
533 pub fn routers_mut(&mut self) -> Vec<&mut Router> {
534 self.routers.iter_mut().collect()
535 }
536
537 pub fn log_protect(&self) -> &Vec<String> {
538 &self.log_protect
539 }
540
541 pub fn gateway(&self) -> Option<&BusClient> {
542 self.gateway.as_ref()
543 }
544 pub fn gateway_mut(&mut self) -> Option<&mut BusClient> {
545 self.gateway.as_mut()
546 }
547
548 pub fn client(&self) -> &BusClient {
549 &self.client
550 }
551 pub fn client_mut(&mut self) -> &mut BusClient {
552 &mut self.client
553 }
554 pub fn hostname(&self) -> &str {
555 &self.hostname
556 }
557
558 pub fn get_router_conf(&self, domain: &str) -> Option<&Router> {
559 self.routers
560 .iter()
561 .find(|r| r.client().domain().name().eq(domain))
562 }
563
564 pub fn set_hostname(&mut self, hostname: &str) {
566 self.hostname = hostname.to_string();
567 }
568
569 fn get_os_hostname() -> Result<String, String> {
570 match gethostname().into_string() {
571 Ok(h) => Ok(h),
572 Err(e) => Err(format!("Cannot read OS host name: {e:?}")),
573 }
574 }
575}