sentinel_proxy/reload/
validators.rs1use sentinel_common::errors::{SentinelError, SentinelResult};
7use sentinel_config::Config;
8use tracing::{debug, trace, warn};
9
10use super::ConfigValidator;
11
12pub struct RouteValidator;
18
19#[async_trait::async_trait]
20impl ConfigValidator for RouteValidator {
21 async fn validate(&self, config: &Config) -> SentinelResult<()> {
22 trace!(route_count = config.routes.len(), "Running route validator");
23
24 for route in &config.routes {
29 trace!(route_id = %route.id, "Validating route");
30
31 if route.upstream.is_some() && route.static_files.is_some() {
32 warn!(
33 route_id = %route.id,
34 "Route has both upstream and static-files configured"
35 );
36 return Err(SentinelError::Config {
37 message: format!(
38 "Route '{}' has both 'upstream' and 'static-files' configured.\n\
39 A route can only be one type. Choose either:\n\
40 - Remove 'upstream' to serve static files\n\
41 - Remove 'static-files' to proxy to upstream",
42 route.id
43 ),
44 source: None,
45 });
46 }
47 }
48
49 for route in &config.routes {
51 if let Some(ref static_config) = route.static_files {
52 trace!(
53 route_id = %route.id,
54 root = %static_config.root.display(),
55 "Checking static files root directory"
56 );
57
58 if !static_config.root.exists() {
59 warn!(
60 route_id = %route.id,
61 root = %static_config.root.display(),
62 "Static files root directory does not exist"
63 );
64 return Err(SentinelError::Config {
65 message: format!(
66 "Route '{}' static files root directory '{}' does not exist.\n\
67 Hint: Create the directory or update the path:\n\
68 \n\
69 mkdir -p {}\n\
70 \n\
71 Or change the configuration:\n\
72 static-files {{\n\
73 root \"/path/to/existing/directory\"\n\
74 }}",
75 route.id,
76 static_config.root.display(),
77 static_config.root.display()
78 ),
79 source: None,
80 });
81 }
82
83 if !static_config.root.is_dir() {
84 warn!(
85 route_id = %route.id,
86 root = %static_config.root.display(),
87 "Static files root is not a directory"
88 );
89 return Err(SentinelError::Config {
90 message: format!(
91 "Route '{}' static files root '{}' is not a directory.\n\
92 The 'root' must be a directory path, not a file.",
93 route.id,
94 static_config.root.display()
95 ),
96 source: None,
97 });
98 }
99 }
100 }
101
102 debug!("Route validation passed");
103 Ok(())
104 }
105
106 fn name(&self) -> &str {
107 "RouteValidator"
108 }
109}
110
111pub struct UpstreamValidator;
117
118#[async_trait::async_trait]
119impl ConfigValidator for UpstreamValidator {
120 async fn validate(&self, config: &Config) -> SentinelResult<()> {
121 trace!(upstream_count = config.upstreams.len(), "Running upstream validator");
122
123 for (name, upstream) in &config.upstreams {
127 trace!(
128 upstream_id = %name,
129 target_count = upstream.targets.len(),
130 "Validating upstream"
131 );
132
133 for (i, target) in upstream.targets.iter().enumerate() {
135 trace!(
136 upstream_id = %name,
137 target_index = i,
138 address = %target.address,
139 "Validating target address"
140 );
141
142 if target.address.parse::<std::net::SocketAddr>().is_ok() {
144 continue;
145 }
146
147 let parts: Vec<&str> = target.address.rsplitn(2, ':').collect();
149 if parts.len() == 2 {
150 if let Ok(port) = parts[0].parse::<u16>() {
151 if port > 0 {
152 continue; }
154 }
155 }
156
157 warn!(
158 upstream_id = %name,
159 target_index = i,
160 address = %target.address,
161 "Invalid target address format"
162 );
163
164 return Err(SentinelError::Config {
165 message: format!(
166 "Upstream '{}' target #{} has invalid address '{}'.\n\
167 \n\
168 Expected format: HOST:PORT\n\
169 \n\
170 Valid examples:\n\
171 - 127.0.0.1:8080 (IPv4)\n\
172 - [::1]:8080 (IPv6)\n\
173 - backend.local:8080 (hostname)\n\
174 - api-server:3000 (service name)",
175 name,
176 i + 1,
177 target.address
178 ),
179 source: None,
180 });
181 }
182
183 if upstream.targets.len() == 1 {
185 warn!(
186 upstream_id = %name,
187 "Upstream has only one target - consider adding more for redundancy"
188 );
189 }
190 }
191
192 debug!("Upstream validation passed");
193 Ok(())
194 }
195
196 fn name(&self) -> &str {
197 "UpstreamValidator"
198 }
199}