1use serde::{Deserialize, Serialize};
58use std::fmt::{Display, Formatter};
59use std::fs;
60use std::net::IpAddr;
61use std::ops::{Deref, DerefMut};
62use std::path::PathBuf;
63use std::str::FromStr;
64
65#[cfg(feature = "cli")]
66use clap::Parser;
67#[cfg(feature = "cli")]
68use std::path::Path;
69
70use crate::scheme::SchemeConfig;
71
72#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct LiteralOrEnv<T>(T, Option<String>);
86
87impl<T> LiteralOrEnv<T> {
88 pub fn from_literal(value: T) -> Self {
89 Self(value, None)
90 }
91
92 #[allow(dead_code)]
94 pub fn inner(&self) -> &T {
95 &self.0
96 }
97
98 #[allow(dead_code)]
100 pub fn into_inner(self) -> T {
101 self.0
102 }
103
104 fn parse_env_var_syntax(s: &str) -> Option<String> {
107 if s.starts_with("${") && s.ends_with('}') {
108 Some(s[2..s.len() - 1].to_string())
110 } else if s.starts_with('$') && s.len() > 1 {
111 let var_name = &s[1..];
113 if var_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
114 Some(var_name.to_string())
115 } else {
116 None
117 }
118 } else {
119 None
120 }
121 }
122}
123
124impl<T> Deref for LiteralOrEnv<T> {
125 type Target = T;
126
127 fn deref(&self) -> &Self::Target {
128 &self.0
129 }
130}
131
132impl<T> DerefMut for LiteralOrEnv<T> {
133 fn deref_mut(&mut self) -> &mut Self::Target {
134 &mut self.0
135 }
136}
137
138impl<'de, T> Deserialize<'de> for LiteralOrEnv<T>
139where
140 T: FromStr,
141 T::Err: std::fmt::Display,
142{
143 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
144 where
145 D: serde::Deserializer<'de>,
146 {
147 let s = String::deserialize(deserializer)?;
148
149 let (value, var_name) = if let Some(var_name) = Self::parse_env_var_syntax(&s) {
151 let value = std::env::var(&var_name).map_err(|_| {
152 serde::de::Error::custom(format!(
153 "Environment variable '{}' not found (referenced as '{}')",
154 var_name, s
155 ))
156 })?;
157 (value, Some(var_name))
158 } else {
159 (s, None)
160 };
161
162 let parsed = value
164 .parse::<T>()
165 .map_err(|e| serde::de::Error::custom(format!("Failed to parse value: {}", e)))?;
166
167 Ok(LiteralOrEnv(parsed, var_name))
168 }
169}
170
171impl<T> serde::Serialize for LiteralOrEnv<T>
172where
173 T: Serialize,
174{
175 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
176 where
177 S: serde::Serializer,
178 {
179 self.0.serialize(serializer)
180 }
181}
182
183impl<T> Display for LiteralOrEnv<T>
184where
185 T: Display,
186{
187 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
188 match self.1.as_ref() {
189 None => self.0.fmt(f),
190 Some(var_name) => write!(f, "${}", var_name),
191 }
192 }
193}
194
195#[derive(Debug)]
197#[cfg_attr(feature = "cli", derive(Parser))]
198#[cfg_attr(feature = "cli", command(name = "x402-rs"))]
199#[cfg_attr(feature = "cli", command(about = "x402 Facilitator HTTP server"))]
200#[allow(dead_code)] pub struct CliArgs {
202 #[cfg_attr(
204 feature = "cli",
205 arg(long, short, env = "CONFIG", default_value = "config.json")
206 )]
207 pub config: PathBuf,
208}
209
210#[derive(Debug, Clone, Deserialize)]
215pub struct Config<TChainsConfig> {
216 #[serde(default = "config_defaults::default_port")]
217 port: u16,
218 #[serde(default = "config_defaults::default_host")]
219 host: IpAddr,
220 #[serde(default)]
221 chains: TChainsConfig,
222 #[serde(default)]
223 schemes: Vec<SchemeConfig>,
224}
225
226impl<TChainsConfig> Default for Config<TChainsConfig>
227where
228 TChainsConfig: Default,
229{
230 fn default() -> Self {
231 Config {
232 port: config_defaults::default_port(),
233 host: config_defaults::default_host(),
234 chains: TChainsConfig::default(),
235 schemes: Vec::new(),
236 }
237 }
238}
239
240pub mod config_defaults {
241 use std::env;
242 use std::net::IpAddr;
243
244 pub const DEFAULT_PORT: u16 = 8080;
245 pub const DEFAULT_HOST: &str = "0.0.0.0";
246
247 pub fn default_port() -> u16 {
249 env::var("PORT")
250 .ok()
251 .and_then(|s| s.parse().ok())
252 .unwrap_or(DEFAULT_PORT)
253 }
254
255 pub fn default_host() -> IpAddr {
257 env::var("HOST")
258 .ok()
259 .and_then(|s| s.parse().ok())
260 .unwrap_or(IpAddr::V4(DEFAULT_HOST.parse().unwrap()))
261 }
262}
263
264impl<TChainsConfig> Config<TChainsConfig> {
265 pub fn port(&self) -> u16 {
267 self.port
268 }
269
270 pub fn host(&self) -> IpAddr {
274 self.host
275 }
276
277 pub fn schemes(&self) -> &Vec<SchemeConfig> {
281 &self.schemes
282 }
283
284 pub fn chains(&self) -> &TChainsConfig {
288 &self.chains
289 }
290}
291
292impl<TChainsConfig> Config<TChainsConfig>
293where
294 TChainsConfig: Default + for<'de> Deserialize<'de>,
295{
296 #[cfg(feature = "cli")]
305 pub fn load() -> Result<Self, ConfigError> {
306 let cli_args = CliArgs::parse();
307 let config_path = Path::new(&cli_args.config)
308 .canonicalize()
309 .map_err(|e| ConfigError::FileRead(cli_args.config, e))?;
310 Self::load_from_path(config_path)
311 }
312
313 pub fn load_from_path(path: PathBuf) -> Result<Self, ConfigError> {
315 let content = fs::read_to_string(&path).map_err(|e| ConfigError::FileRead(path, e))?;
316 let config: Config<TChainsConfig> = serde_json::from_str(&content)?;
317 Ok(config)
318 }
319}
320
321#[derive(Debug, thiserror::Error)]
323pub enum ConfigError {
324 #[error("Failed to read config file at {0}: {1}")]
325 FileRead(PathBuf, std::io::Error),
326 #[error("Failed to parse config file: {0}")]
327 JsonParse(#[from] serde_json::Error),
328}