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!(
122 upstream_count = config.upstreams.len(),
123 "Running upstream validator"
124 );
125
126 for (name, upstream) in &config.upstreams {
130 trace!(
131 upstream_id = %name,
132 target_count = upstream.targets.len(),
133 "Validating upstream"
134 );
135
136 for (i, target) in upstream.targets.iter().enumerate() {
138 trace!(
139 upstream_id = %name,
140 target_index = i,
141 address = %target.address,
142 "Validating target address"
143 );
144
145 if target.address.parse::<std::net::SocketAddr>().is_ok() {
147 continue;
148 }
149
150 let parts: Vec<&str> = target.address.rsplitn(2, ':').collect();
152 if parts.len() == 2 {
153 if let Ok(port) = parts[0].parse::<u16>() {
154 if port > 0 {
155 continue; }
157 }
158 }
159
160 warn!(
161 upstream_id = %name,
162 target_index = i,
163 address = %target.address,
164 "Invalid target address format"
165 );
166
167 return Err(SentinelError::Config {
168 message: format!(
169 "Upstream '{}' target #{} has invalid address '{}'.\n\
170 \n\
171 Expected format: HOST:PORT\n\
172 \n\
173 Valid examples:\n\
174 - 127.0.0.1:8080 (IPv4)\n\
175 - [::1]:8080 (IPv6)\n\
176 - backend.local:8080 (hostname)\n\
177 - api-server:3000 (service name)",
178 name,
179 i + 1,
180 target.address
181 ),
182 source: None,
183 });
184 }
185
186 if upstream.targets.len() == 1 {
188 warn!(
189 upstream_id = %name,
190 "Upstream has only one target - consider adding more for redundancy"
191 );
192 }
193 }
194
195 debug!("Upstream validation passed");
196 Ok(())
197 }
198
199 fn name(&self) -> &str {
200 "UpstreamValidator"
201 }
202}