1use std::collections::HashMap;
6use std::path::PathBuf;
7use std::sync::Arc;
8
9use serde_json::{Map, Value};
10
11use crate::amqp_transport::{
12 AmqpTransportClient, DEFAULT_AMQP_EXCHANGE, DEFAULT_AMQP_EXCHANGE_TYPE,
13};
14use crate::capabilities::AgentCapabilities;
15use crate::key_provider::KeyProvider;
16use crate::messages::DeliveryMode;
17use crate::mqtt_transport::{DEFAULT_MQTT_QOS, DEFAULT_MQTT_TOPIC_PREFIX, MqttTransportClient};
18
19#[derive(Clone)]
20pub struct AcpAgentOptions {
21 pub storage_dir: PathBuf,
22 pub endpoint: Option<String>,
23 pub relay_url: String,
24 pub relay_hints: Vec<String>,
25 pub enterprise_directory_hints: Vec<String>,
26 pub discovery_scheme: String,
27 pub trust_profile: String,
28 pub capabilities: Option<AgentCapabilities>,
29 pub default_delivery_mode: DeliveryMode,
30 pub http_timeout_seconds: u64,
31 pub allow_insecure_http: bool,
32 pub allow_insecure_tls: bool,
33 pub mtls_enabled: bool,
34 pub ca_file: Option<String>,
35 pub cert_file: Option<String>,
36 pub key_file: Option<String>,
37 pub key_provider: String,
38 pub vault_url: Option<String>,
39 pub vault_path: Option<String>,
40 pub vault_token_env: String,
41 pub vault_token: Option<String>,
42 pub key_provider_instance: Option<Arc<dyn KeyProvider>>,
43 pub amqp_broker_url: Option<String>,
44 pub amqp_exchange: String,
45 pub amqp_exchange_type: String,
46 pub amqp_transport: Option<AmqpTransportClient>,
47 pub mqtt_broker_url: Option<String>,
48 pub mqtt_qos: u8,
49 pub mqtt_topic_prefix: String,
50 pub mqtt_transport: Option<MqttTransportClient>,
51 pub extra: HashMap<String, Value>,
52}
53
54impl Default for AcpAgentOptions {
55 fn default() -> Self {
56 Self {
57 storage_dir: PathBuf::from(".acp-data"),
58 endpoint: None,
59 relay_url: "https://localhost:8080".to_string(),
60 relay_hints: Vec::new(),
61 enterprise_directory_hints: Vec::new(),
62 discovery_scheme: "https".to_string(),
63 trust_profile: "self_asserted".to_string(),
64 capabilities: None,
65 default_delivery_mode: DeliveryMode::Auto,
66 http_timeout_seconds: 10,
67 allow_insecure_http: false,
68 allow_insecure_tls: false,
69 mtls_enabled: false,
70 ca_file: None,
71 cert_file: None,
72 key_file: None,
73 key_provider: "local".to_string(),
74 vault_url: None,
75 vault_path: None,
76 vault_token_env: "VAULT_TOKEN".to_string(),
77 vault_token: None,
78 key_provider_instance: None,
79 amqp_broker_url: None,
80 amqp_exchange: DEFAULT_AMQP_EXCHANGE.to_string(),
81 amqp_exchange_type: DEFAULT_AMQP_EXCHANGE_TYPE.to_string(),
82 amqp_transport: None,
83 mqtt_broker_url: None,
84 mqtt_qos: DEFAULT_MQTT_QOS,
85 mqtt_topic_prefix: DEFAULT_MQTT_TOPIC_PREFIX.to_string(),
86 mqtt_transport: None,
87 extra: HashMap::new(),
88 }
89 }
90}
91
92impl AcpAgentOptions {
93 pub fn to_config_map(&self) -> Map<String, Value> {
94 let mut values = Map::new();
95 values.insert(
96 "allow_insecure_http".to_string(),
97 Value::Bool(self.allow_insecure_http),
98 );
99 values.insert(
100 "allow_insecure_tls".to_string(),
101 Value::Bool(self.allow_insecure_tls),
102 );
103 values.insert("mtls_enabled".to_string(), Value::Bool(self.mtls_enabled));
104 values.insert(
105 "ca_file".to_string(),
106 self.ca_file
107 .clone()
108 .map(Value::String)
109 .unwrap_or(Value::Null),
110 );
111 values.insert(
112 "cert_file".to_string(),
113 self.cert_file
114 .clone()
115 .map(Value::String)
116 .unwrap_or(Value::Null),
117 );
118 values.insert(
119 "key_file".to_string(),
120 self.key_file
121 .clone()
122 .map(Value::String)
123 .unwrap_or(Value::Null),
124 );
125 values.insert(
126 "key_provider".to_string(),
127 Value::String(self.key_provider.clone()),
128 );
129 values.insert(
130 "vault_url".to_string(),
131 self.vault_url
132 .clone()
133 .map(Value::String)
134 .unwrap_or(Value::Null),
135 );
136 values.insert(
137 "vault_path".to_string(),
138 self.vault_path
139 .clone()
140 .map(Value::String)
141 .unwrap_or(Value::Null),
142 );
143 values.insert(
144 "vault_token_env".to_string(),
145 Value::String(self.vault_token_env.clone()),
146 );
147 values
148 }
149
150 pub fn from_config_map(config: Option<&Map<String, Value>>) -> Self {
151 let mut options = Self::default();
152 let Some(config) = config else {
153 return options;
154 };
155 options.allow_insecure_http = as_bool(config.get("allow_insecure_http"), false);
156 options.allow_insecure_tls = as_bool(config.get("allow_insecure_tls"), false);
157 options.mtls_enabled = as_bool(config.get("mtls_enabled"), false);
158 options.ca_file = as_string(config.get("ca_file"));
159 options.cert_file = as_string(config.get("cert_file"));
160 options.key_file = as_string(config.get("key_file"));
161 options.key_provider =
162 as_string(config.get("key_provider")).unwrap_or_else(|| "local".to_string());
163 options.vault_url = as_string(config.get("vault_url"));
164 options.vault_path = as_string(config.get("vault_path"));
165 options.vault_token_env =
166 as_string(config.get("vault_token_env")).unwrap_or_else(|| "VAULT_TOKEN".to_string());
167 options
168 }
169}
170
171fn as_bool(value: Option<&Value>, default_value: bool) -> bool {
172 match value {
173 Some(Value::Bool(v)) => *v,
174 Some(Value::String(v)) => match v.trim().to_lowercase().as_str() {
175 "1" | "true" | "yes" | "on" => true,
176 "0" | "false" | "no" | "off" => false,
177 _ => default_value,
178 },
179 _ => default_value,
180 }
181}
182
183fn as_string(value: Option<&Value>) -> Option<String> {
184 value
185 .and_then(Value::as_str)
186 .map(str::trim)
187 .filter(|v| !v.is_empty())
188 .map(str::to_string)
189}