agent_first_pay/
config.rs1use crate::types::*;
2use agent_first_data::cli_parse_log_filters;
3use std::path::Path;
4
5pub const VERSION: &str = env!("CARGO_PKG_VERSION");
6
7pub fn enabled_features() -> Vec<&'static str> {
8 let features: &[Option<&str>] = &[
9 #[cfg(feature = "redb")]
10 Some("redb"),
11 #[cfg(feature = "postgres")]
12 Some("postgres"),
13 #[cfg(feature = "cashu")]
14 Some("cashu"),
15 #[cfg(feature = "ln-nwc")]
16 Some("ln-nwc"),
17 #[cfg(feature = "ln-phoenixd")]
18 Some("ln-phoenixd"),
19 #[cfg(feature = "ln-lnbits")]
20 Some("ln-lnbits"),
21 #[cfg(feature = "sol")]
22 Some("sol"),
23 #[cfg(feature = "evm")]
24 Some("evm"),
25 #[cfg(feature = "btc-esplora")]
26 Some("btc-esplora"),
27 #[cfg(feature = "btc-core")]
28 Some("btc-core"),
29 #[cfg(feature = "btc-electrum")]
30 Some("btc-electrum"),
31 #[cfg(feature = "interactive")]
32 Some("interactive"),
33 #[cfg(feature = "rest")]
34 Some("rest"),
35 ];
36 features.iter().copied().flatten().collect()
37}
38
39pub fn build_startup_log(
41 argv: Option<Vec<String>>,
42 config: Option<&RuntimeConfig>,
43 args: serde_json::Value,
44) -> Output {
45 Output::Log {
46 event: "startup".to_string(),
47 request_id: None,
48 version: Some(VERSION.to_string()),
49 argv,
50 config: config.map(|c| serde_json::to_value(c).unwrap_or(serde_json::Value::Null)),
51 args: Some(args),
52 env: Some(serde_json::json!({
53 "features": enabled_features(),
54 })),
55 trace: Trace::from_duration(0),
56 }
57}
58
59pub fn should_emit_startup_log(log_filters: &[String], startup_requested: bool) -> bool {
62 startup_requested || !log_filters.is_empty()
63}
64
65pub fn maybe_startup_log(
67 log_filters: &[String],
68 startup_requested: bool,
69 argv: Option<Vec<String>>,
70 config: Option<&RuntimeConfig>,
71 args: serde_json::Value,
72) -> Option<Output> {
73 if !should_emit_startup_log(log_filters, startup_requested) {
74 return None;
75 }
76 Some(build_startup_log(argv, config, args))
77}
78
79impl RuntimeConfig {
80 pub fn load_from_dir(data_dir: &str) -> Result<Self, String> {
82 let path = Path::new(data_dir).join("config.toml");
83 if !path.exists() {
84 return Ok(Self {
85 data_dir: data_dir.to_string(),
86 ..Self::default()
87 });
88 }
89 let contents =
90 std::fs::read_to_string(&path).map_err(|e| format!("read {}: {e}", path.display()))?;
91 let mut cfg: Self =
92 toml::from_str(&contents).map_err(|e| format!("parse {}: {e}", path.display()))?;
93 cfg.data_dir = data_dir.to_string();
95 Ok(cfg)
96 }
97
98 #[allow(dead_code)]
99 pub fn apply_update(&mut self, patch: ConfigPatch) {
100 if let Some(v) = patch.data_dir {
101 self.data_dir = v;
102 }
103 if let Some(v) = patch.limits {
104 self.limits = v;
105 }
106 if let Some(v) = patch.log {
107 self.log = cli_parse_log_filters(&v);
108 }
109 if let Some(rpc_nodes) = patch.afpay_rpc {
110 for (name, cfg) in rpc_nodes {
111 self.afpay_rpc.insert(name, cfg);
112 }
113 }
114 if let Some(providers) = patch.providers {
115 for (network, rpc_name) in providers {
116 self.providers.insert(network, rpc_name);
117 }
118 }
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn maybe_startup_log_disabled_without_filters_or_request() {
128 let out = maybe_startup_log(&[], false, None, None, serde_json::json!({"mode": "test"}));
129 assert!(out.is_none());
130 }
131
132 #[test]
133 fn maybe_startup_log_enabled_with_filters() {
134 let filters = vec!["cashu".to_string()];
135 let out = maybe_startup_log(
136 &filters,
137 false,
138 None,
139 None,
140 serde_json::json!({"mode": "test"}),
141 );
142 assert!(out.is_some());
143 }
144
145 #[test]
146 fn maybe_startup_log_enabled_with_explicit_request() {
147 let out = maybe_startup_log(&[], true, None, None, serde_json::json!({"mode": "test"}));
148 assert!(out.is_some());
149 }
150}