1use std::{
2 net::{Ipv4Addr, SocketAddr},
3 path::PathBuf,
4 str::FromStr,
5};
6
7use anyhow::{Result, anyhow};
8use log::LevelFilter;
9use serde::Deserialize;
10use url::Url;
11
12const DEFAULT_PORT: u16 = 3000;
13const DEFAULT_HOST: url::Host = url::Host::Ipv4(Ipv4Addr::new(127, 0, 0, 1));
14const DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::Info;
15
16#[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)]
18pub struct Config {
19 pub server: ServerConfig,
20 #[serde(default)]
21 pub cache: CacheConfig,
22}
23
24#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
25pub struct ServerConfig {
26 #[serde(default = "default_port")]
27 pub port: u16,
28 #[serde(deserialize_with = "deserialize_host", default = "default_host")]
29 pub host: url::Host,
30 #[serde(
31 deserialize_with = "deserialize_log_level",
32 default = "default_log_level"
33 )]
34 pub log_level: LevelFilter,
35 #[serde(deserialize_with = "deserialize_sources")]
36 pub sources: Vec<ImageSource>,
37}
38
39const fn default_port() -> u16 {
40 DEFAULT_PORT
41}
42const fn default_host() -> url::Host {
43 DEFAULT_HOST
44}
45const fn default_log_level() -> LevelFilter {
46 DEFAULT_LOG_LEVEL
47}
48
49fn deserialize_host<'de, D>(deserializer: D) -> Result<url::Host, D::Error>
50where
51 D: serde::Deserializer<'de>,
52{
53 let s: String = Deserialize::deserialize(deserializer)?;
54 url::Host::parse(&s).map_err(serde::de::Error::custom)
55}
56
57fn deserialize_log_level<'de, D>(deserializer: D) -> Result<LevelFilter, D::Error>
58where
59 D: serde::Deserializer<'de>,
60{
61 let level: String = Deserialize::deserialize(deserializer)?;
62 LevelFilter::from_str(&level).map_err(serde::de::Error::custom)
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum ImageSource {
67 Url(Url),
68 Path(PathBuf),
69}
70
71#[derive(Debug, Default, Deserialize, Clone, Copy, PartialEq, Eq)]
72pub struct CacheConfig {
73 pub backend: CacheBackendType,
74}
75
76#[derive(Debug, Default, Deserialize, Clone, Copy, PartialEq, Eq)]
77#[serde(rename_all = "snake_case")]
78pub enum CacheBackendType {
79 #[default]
80 InMemory,
81 FileSystem,
82}
83
84impl FromStr for ImageSource {
85 type Err = anyhow::Error;
86
87 fn from_str(s: &str) -> Result<Self, Self::Err> {
88 if let Ok(url) = Url::parse(s) {
89 Ok(Self::Url(url))
90 } else if PathBuf::from(s).exists() {
91 Ok(Self::Path(PathBuf::from(s).canonicalize()?))
92 } else {
93 Err(anyhow!(
94 "Image source doesn't exist or couldn't be parsed as a URL: {s}"
95 ))
96 }
97 }
98}
99
100impl FromStr for CacheBackendType {
101 type Err = String;
102
103 fn from_str(s: &str) -> Result<Self, Self::Err> {
104 match s.to_lowercase().as_str() {
105 "in_memory" => Ok(Self::InMemory),
106 "file_system" => Ok(Self::FileSystem),
107 _ => Err(format!("Unknown cache backend type: {s}")),
108 }
109 }
110}
111
112fn deserialize_sources<'de, D>(deserializer: D) -> Result<Vec<ImageSource>, D::Error>
113where
114 D: serde::Deserializer<'de>,
115{
116 let sources: Vec<String> = Deserialize::deserialize(deserializer)?;
117 let mut image_sources = Vec::new();
118
119 for source in sources {
120 match ImageSource::from_str(&source) {
121 Ok(image_source) => image_sources.push(image_source),
122 Err(e) => log::warn!("Invalid image source '{source}': {e}"),
123 }
124 }
125
126 if image_sources.is_empty() {
127 return Err(serde::de::Error::custom("No valid image sources found"));
128 }
129
130 Ok(image_sources)
131}
132
133impl Default for ServerConfig {
134 fn default() -> Self {
135 Self {
136 port: DEFAULT_PORT,
137 host: DEFAULT_HOST,
138 log_level: DEFAULT_LOG_LEVEL,
139 sources: vec![],
140 }
141 }
142}
143
144impl Config {
145 pub fn from_file(path: &str) -> Result<Self> {
151 let content = std::fs::read_to_string(path)?;
152 let config: Self = toml::from_str(&content)?;
153 Ok(config)
154 }
155
156 pub fn with_env(self) -> Result<Self> {
170 self.with_env_backend(&crate::env::StdEnvBackend)
171 }
172
173 pub fn with_env_backend(mut self, env: &impl crate::env::EnvBackend) -> Result<Self> {
181 macro_rules! set_from_env {
182 ($field:expr, $var:literal, $parse_fn:expr) => {
183 if let Ok(value) = env.var(concat!("RANDOM_IMAGE_SERVER_", $var)) {
184 $field = $parse_fn(&value).map_err(|e| {
185 anyhow!("Failed to parse environment variable '{}': {}", $var, e)
186 })?
187 }
188 };
189 }
190
191 set_from_env!(self.server.port, "PORT", u16::from_str);
192 set_from_env!(self.server.host, "HOST", url::Host::parse);
193 set_from_env!(self.server.log_level, "LOG_LEVEL", LevelFilter::from_str);
194 set_from_env!(self.server.sources, "SOURCES", |s: &str| {
195 s.split(',')
196 .map(ImageSource::from_str)
197 .collect::<Result<Vec<_>, _>>()
198 .and_then(|sources| {
199 if sources.is_empty() {
200 Err(anyhow!("No valid image sources found"))
201 } else {
202 Ok(sources)
203 }
204 })
205 });
206 set_from_env!(
207 self.cache.backend,
208 "CACHE_BACKEND",
209 CacheBackendType::from_str
210 );
211
212 Ok(self)
213 }
214
215 pub fn socket_addr(&self) -> Result<SocketAddr, std::net::AddrParseError> {
221 format!("{}:{}", self.server.host, self.server.port).parse()
222 }
223}