1use std::collections::HashMap;
5use std::sync::atomic::AtomicBool;
6use std::sync::{Arc, RwLock};
7
8use crate::middleware::WithMiddleware;
9use crate::proxy_config::{
10 ActionConfig, AuthConfig, CompiledRoute, ConfigDrivenApp, DynamicProxy, MiddlewareConfig,
11 ProxyConfig, RedirectAdapter, RespondAdapter, RouteMatcher, StaticAdapter, arc_app,
12 AuthMisconfigured, BearerAuthMiddleware, PerRouteMaxBodySize, PerRouteRateLimit,
13};
14
15pub fn build_from_file() -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
22 let source = std::fs::read_to_string(crate::proxy_config::config_file_path()).unwrap_or_default();
23 let config = ProxyConfig::from_str(&source);
24 let mut report = crate::config_check::ConfigReport::default();
25 config.validate(&mut report);
26 for e in &report.errors {
27 eprintln!("[proxy_config] ERROR: {e}");
28 }
29 for w in &report.warnings {
30 eprintln!("[proxy_config] Warning: {w}");
31 }
32 let (app, handles) = build_with_source(config, Some(source));
33 let reloadable = app.clone();
34 crate::config_reload::on_reload(move || reloadable.reload_from_file());
35 (app, handles)
36}
37
38pub fn build(config: ProxyConfig) -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
40 build_with_source(config, None)
41}
42
43fn build_with_source(config: ProxyConfig, source: Option<String>) -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
44 let health_stop = Arc::new(AtomicBool::new(false));
45 let compiled = compile_routes(&config, &health_stop);
46 let handles = spawn_listeners(&config);
47 (ConfigDrivenApp::new(compiled, config, health_stop, source), handles)
48}
49
50pub(crate) fn compile_routes(config: &ProxyConfig, health_stop: &Arc<AtomicBool>) -> Vec<CompiledRoute> {
54 let authorizer = config_authorizer(config);
56 if let Some(Err(e)) = &authorizer {
57 eprintln!("[proxy_config] ERROR: [authz]: {e} — routes with authz answer 503 until it's fixed.");
58 }
59 crate::authz::config::set_current(authorizer);
60 let mut upstream_lives: HashMap<String, Arc<RwLock<Vec<String>>>> = HashMap::new();
62 let mut upstream_trust: HashMap<String, Arc<crate::proxy::UpstreamTls>> = HashMap::new();
66 let mut upstream_policies: HashMap<String, Arc<crate::proxy_config::upstream::UpstreamPolicy>> = HashMap::new();
69
70 for upstream in &config.upstreams {
71 let live = Arc::new(RwLock::new(upstream.backends.clone()));
72 upstream_lives.insert(upstream.name.clone(), Arc::clone(&live));
73 upstream_policies.insert(
74 upstream.name.clone(),
75 Arc::new(crate::proxy_config::upstream::UpstreamPolicy::new(
76 &upstream.name,
77 Arc::clone(&live),
78 &upstream.backends,
79 &upstream.weights,
80 upstream.sticky.clone(),
81 upstream.circuit_breaker.as_ref(),
82 upstream.retry.clone(),
83 )),
84 );
85
86 let client = upstream.tls_client_cert_file.as_deref().zip(upstream.tls_client_key_file.as_deref());
87 let configured = upstream.tls_ca_file.is_some() || upstream.tls_client_cert_file.is_some() || upstream.tls_client_key_file.is_some();
88 let trust = configured.then(|| {
89 Arc::new(crate::proxy::UpstreamTls::new(upstream.tls_ca_file.as_deref(), client).unwrap_or_else(|e| {
90 eprintln!("[proxy_config] ERROR: upstream '{}': {e} — its TLS backends answer 502 until this is fixed", upstream.name);
91 crate::proxy::UpstreamTls::reject_all(&upstream.name)
92 }))
93 });
94 if let Some(trust) = &trust {
95 upstream_trust.insert(upstream.name.clone(), Arc::clone(trust));
96 }
97
98 if let Some(ref hc) = upstream.health_check {
99 crate::proxy_config::health::start_health_checker(
100 upstream.name.clone(),
101 upstream.backends.clone(),
102 Arc::clone(&live),
103 hc.clone(),
104 Arc::clone(health_stop),
105 trust,
106 );
107 }
108 }
109
110 crate::proxy_config::upstream::register(upstream_policies.values().cloned().collect());
111
112 let mut caches: Vec<(String, crate::cache::CacheLayer)> = Vec::new();
114 let mut compiled: Vec<CompiledRoute> = Vec::new();
115
116 for route in &config.routes {
117 let matcher = RouteMatcher::from_match_config(&route.match_);
118
119 let mut async_proxy: Option<Arc<DynamicProxy>> = None;
120 let cache_name = format!("route:{}", if route.name.is_empty() { format!("#{}", compiled.len() + 1) } else { route.name.clone() });
121 let mirror = route.middleware.mirror.as_ref().map(|m| build_mirror(&route.name, m, &upstream_lives));
122
123 let base_handler: Arc<dyn crate::application::Application + Send + Sync> =
125 match &route.action {
126 ActionConfig::Proxy {
127 upstream,
128 connect_timeout_ms,
129 read_timeout_ms,
130 strip_path_prefix,
131 add_path_prefix,
132 upstream_host,
133 } => {
134 let live = upstream_lives
135 .get(upstream.as_str())
136 .cloned()
137 .unwrap_or_else(|| {
138 Arc::new(RwLock::new(vec![upstream.clone()]))
140 });
141 let upstream_cfg = config.upstreams.iter().find(|u| u.name == *upstream);
142 let upstream_tls = upstream_cfg.map_or_else(|| upstream.starts_with("https://"), |u| u.tls);
143 let upstream_strategy = upstream_cfg
144 .map(|u| u.strategy.clone())
145 .unwrap_or_else(|| "round_robin".to_string());
146 let proxy = Arc::new(DynamicProxy::new(
147 live,
148 *connect_timeout_ms,
149 *read_timeout_ms,
150 strip_path_prefix.clone(),
151 add_path_prefix.clone(),
152 upstream_tls,
153 upstream_strategy,
154 )
155 .with_trust(upstream_trust.get(upstream.as_str()).cloned())
156 .with_policy(upstream, upstream_policies.get(upstream.as_str()).cloned())
157 .with_mirror(mirror.clone())
158 .with_early_hints(&route.middleware.early_hints)
159 .with_h3(upstream_cfg.is_some_and(|u| u.protocol.eq_ignore_ascii_case("h3")))
160 .with_upstream_host(upstream_host.clone()));
161 if proxy.supports_async() && route.middleware.is_empty() {
162 async_proxy = Some(Arc::clone(&proxy));
163 }
164 proxy
165 }
166
167 ActionConfig::Redirect { location, status } => {
168 arc_app(RedirectAdapter::new(location.clone(), *status))
169 }
170
171 ActionConfig::Respond { status, body, content_type } => {
172 arc_app(RespondAdapter::new(*status, body.clone(), content_type.clone()))
173 }
174
175 ActionConfig::Static { root, index } => {
176 arc_app(StaticAdapter::new(root.clone(), index.clone()))
177 }
178
179 ActionConfig::Grpc { upstream, connect_timeout_ms, read_timeout_ms } => {
182 let live = upstream_lives
183 .get(upstream.as_str())
184 .cloned()
185 .unwrap_or_else(|| Arc::new(RwLock::new(vec![upstream.clone()])));
186 let upstream_cfg = config.upstreams.iter().find(|u| u.name == *upstream);
187 let upstream_tls = upstream_cfg.map_or_else(|| upstream.starts_with("https://"), |u| u.tls);
188 #[cfg(feature = "http2")]
189 {
190 let _ = upstream_cfg;
191 arc_app(crate::proxy_config::H2Route::new(
192 live,
193 upstream_tls,
194 upstream_trust.get(upstream.as_str()).cloned(),
195 *connect_timeout_ms,
196 *read_timeout_ms,
197 ))
198 }
199 #[cfg(not(feature = "http2"))]
200 {
201 let upstream_strategy = upstream_cfg
202 .map(|u| u.strategy.clone())
203 .unwrap_or_else(|| "round_robin".to_string());
204 arc_app(
205 DynamicProxy::new(live, *connect_timeout_ms, *read_timeout_ms, None, None, upstream_tls, upstream_strategy)
206 .with_trust(upstream_trust.get(upstream.as_str()).cloned())
207 .with_policy(upstream, upstream_policies.get(upstream.as_str()).cloned()),
208 )
209 }
210 }
211
212 ActionConfig::Mcp(gateway) => arc_app(crate::proxy_config::mcp_gateway::GatewayAdapter::start(
213 (**gateway).clone(),
214 route.match_.path.clone().unwrap_or_else(|| "/mcp".to_string()),
215 )),
216 ActionConfig::Llm(llm) => arc_app(crate::proxy_config::llm::build(llm)),
217 ActionConfig::A2a(gateway) => arc_app(crate::proxy_config::a2a_gateway::A2aGateway::new(
218 (**gateway).clone(),
219 route.match_.path.as_deref().unwrap_or("/"),
220 )),
221 ActionConfig::Unknown(_) => {
222 arc_app(crate::proxy_config::NullApp)
224 }
225 };
226
227 let mut handler = apply_middleware_named(base_handler, &route.middleware, &cache_name, &mut caches);
231 if !matches!(route.action, ActionConfig::Proxy { .. }) {
232 if let Some(mirror) = &mirror {
233 handler = arc_app(WithMiddleware::new(ArcApp(handler)).wrap(crate::proxy::mirror::MirrorLayer::new(mirror.clone())));
234 }
235 if !route.middleware.early_hints.is_empty() {
236 let layer = crate::early_hints::EarlyHintsLayer::new(route.middleware.early_hints.iter().cloned());
237 handler = arc_app(WithMiddleware::new(ArcApp(handler)).wrap(layer));
238 }
239 }
240
241 compiled.push(CompiledRoute { matcher, handler, async_proxy });
242 }
243 crate::cache::registry::replace_group("route:", caches);
244 compiled
245}
246
247fn spawn_listeners(config: &ProxyConfig) -> Vec<std::thread::JoinHandle<()>> {
251 let mut handles: Vec<std::thread::JoinHandle<()>> = Vec::new();
252
253 for tcp_cfg in &config.tcp_proxies {
254 let listen = tcp_cfg.listen.clone();
255 let backends = tcp_cfg.backends.clone();
256 let timeout_ms = tcp_cfg.connect_timeout_ms;
257 let name = tcp_cfg.name.clone();
258 let h = std::thread::Builder::new()
259 .name(format!("tcp-proxy-{}", name))
260 .spawn(move || {
261 let proxy = crate::tcp_proxy::TcpProxy::new(backends)
262 .connect_timeout_ms(timeout_ms);
263 if let Err(e) = proxy.bind(&listen) {
264 eprintln!("[tcp_proxy:{}] {}", name, e);
265 }
266 })
267 .expect("failed to spawn tcp proxy thread");
268 handles.push(h);
269 }
270
271 for udp_cfg in &config.udp_proxies {
272 let listen = udp_cfg.listen.clone();
273 let backends = udp_cfg.backends.clone();
274 let reply_timeout_ms = udp_cfg.reply_timeout_ms;
275 let buffer_size = udp_cfg.buffer_size;
276 let name = udp_cfg.name.clone();
277 let h = std::thread::Builder::new()
278 .name(format!("udp-proxy-{}", name))
279 .spawn(move || {
280 let proxy = crate::udp_proxy::UdpProxy::new(backends)
281 .reply_timeout_ms(reply_timeout_ms)
282 .buffer_size(buffer_size);
283 if let Err(e) = proxy.bind(&listen) {
284 eprintln!("[udp_proxy:{}] {}", name, e);
285 }
286 })
287 .expect("failed to spawn udp proxy thread");
288 handles.push(h);
289 }
290
291 for ws_cfg in &config.ws_proxies {
292 let listen = ws_cfg.listen.clone();
293 let backends = ws_cfg.backends.clone();
294 let connect_timeout_ms = ws_cfg.connect_timeout_ms;
295 let read_timeout_ms = ws_cfg.read_timeout_ms;
296 let name = ws_cfg.name.clone();
297
298 let live = Arc::new(RwLock::new(backends.clone()));
302 if let Some(ref hc) = ws_cfg.health_check {
303 crate::proxy_config::health::start_health_checker(
304 name.clone(),
305 backends.clone(),
306 Arc::clone(&live),
307 hc.clone(),
308 Arc::new(AtomicBool::new(false)),
309 None,
310 );
311 }
312
313 let h = std::thread::Builder::new()
314 .name(format!("ws-proxy-{}", name))
315 .spawn(move || {
316 let proxy = crate::ws_proxy::WsProxy::with_live_backends(backends, live)
317 .connect_timeout_ms(connect_timeout_ms)
318 .read_timeout_ms(read_timeout_ms);
319 if let Err(e) = proxy.bind(&listen) {
320 eprintln!("[ws_proxy:{}] {}", name, e);
321 }
322 })
323 .expect("failed to spawn ws proxy thread");
324 handles.push(h);
325 }
326
327 handles
328}
329
330fn build_mirror(route: &str, config: &crate::proxy_config::MirrorConfig, lives: &HashMap<String, Arc<RwLock<Vec<String>>>>) -> crate::proxy::mirror::Mirror {
345 let name = if config.upstream.is_empty() { format!("{route}-mirror") } else { config.upstream.clone() };
346 let mirror = match lives.get(&config.upstream) {
347 Some(live) if !config.upstream.is_empty() => crate::proxy::mirror::Mirror::with_live_backends(name, Arc::clone(live)),
348 _ if !config.upstream.is_empty() => crate::proxy::mirror::Mirror::new(name, vec![config.upstream.clone()]),
349 _ => crate::proxy::mirror::Mirror::new(name, config.backends.clone()),
350 };
351 mirror.percent(if config.percent.is_finite() { config.percent } else { 0.0 })
352}
353
354pub(crate) fn apply_middleware(
355 handler: Arc<dyn crate::application::Application + Send + Sync>,
356 mw: &MiddlewareConfig,
357) -> Arc<dyn crate::application::Application + Send + Sync> {
358 apply_middleware_named(handler, mw, "middleware", &mut Vec::new())
359}
360
361pub(crate) fn apply_middleware_named(
364 handler: Arc<dyn crate::application::Application + Send + Sync>,
365 mw: &MiddlewareConfig,
366 name: &str,
367 caches: &mut Vec<(String, crate::cache::CacheLayer)>,
368) -> Arc<dyn crate::application::Application + Send + Sync> {
369 let mut app: Arc<dyn crate::application::Application + Send + Sync> = handler;
372
373 if let Some(ref cache_cfg) = mw.cache {
375 let mut layer = if cache_cfg.dir.is_empty() {
376 crate::cache::CacheLayer::memory(1000)
377 } else {
378 let max = crate::proxy_config::cache_max_bytes(&cache_cfg.max_size).unwrap_or(256 * 1024 * 1024);
379 crate::cache::CacheLayer::disk(&cache_cfg.dir, max).unwrap_or_else(|e| {
380 eprintln!("[proxy_config] ERROR: cache dir {}: {e} — this route caches in memory instead", cache_cfg.dir);
381 crate::cache::CacheLayer::memory(1000)
382 })
383 }
384 .ttl(cache_cfg.ttl_secs);
385 if !cache_cfg.purge_token_env.is_empty() {
386 layer = layer.purge_token(std::env::var(&cache_cfg.purge_token_env).unwrap_or_default().trim());
387 }
388 caches.push((name.to_string(), layer.clone()));
389 for vh in &cache_cfg.vary_by {
390 layer = layer.vary_by_header(vh.as_str());
391 }
392 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer));
393 }
394
395 if !mw.rewrite_request.is_empty() || !mw.rewrite_response.is_empty() {
397 let mut layer = crate::rewrite::RewriteLayer::new();
398 for rule in &mw.rewrite_request {
399 layer = apply_request_rewrite_rule(layer, rule);
400 }
401 for rule in &mw.rewrite_response {
402 layer = apply_response_rewrite_rule(layer, rule);
403 }
404 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer));
405 }
406
407 if let Some(ref wasm) = mw.wasm {
409 #[cfg(feature = "wasm-plugins")]
410 let layer = crate::wasm_plugin::WasmLayer::from_file(&wasm.module)
411 .map(|l| l.fuel(wasm.fuel).memory_mb(wasm.memory_mb).config(wasm.config.clone()));
412 #[cfg(not(feature = "wasm-plugins"))]
413 let layer: Result<crate::proxy_config::PluginMisconfigured, String> =
414 Err(format!("plugin {}: this rws binary was built without the wasm-plugins feature", wasm.module));
415 app = match layer {
416 Ok(layer) => arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer)),
417 Err(e) => {
418 eprintln!("[proxy_config] ERROR: {e} — this route answers 503 until the plugin is fixed.");
419 arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(crate::proxy_config::PluginMisconfigured))
420 }
421 };
422 }
423
424 if let Some(ref rule) = mw.authz {
426 let layer = match crate::authz::config::current() {
427 Some(Ok(authorizer)) => crate::authz::AuthzLayer::new(authorizer, &rule.relation, &rule.object),
428 Some(Err(e)) => Err(format!("[authz] couldn't be built: {e}")),
429 None => Err("the route has [route.middleware.authz] but there's no [authz] section".to_string()),
430 };
431 app = match layer {
432 Ok(layer) => arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer)),
433 Err(e) => {
434 eprintln!("[proxy_config] ERROR: authz: {e} — this route answers 503 until it's fixed.");
435 arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(AuthMisconfigured))
436 }
437 };
438 }
439
440 if let Some(ref auth_cfg) = mw.auth {
445 let deny = |reason: String| -> Arc<dyn crate::application::Application + Send + Sync> {
446 eprintln!(
447 "[proxy_config] ERROR: {reason} — this route answers 503 until the auth configuration is fixed."
448 );
449 arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(AuthMisconfigured))
450 };
451 app = match auth_cfg {
452 AuthConfig::Bearer { token_env } => {
453 let token = std::env::var(token_env).unwrap_or_default();
454 if token.is_empty() {
455 deny(format!("bearer auth: env var '{token_env}' is unset or empty"))
456 } else {
457 arc_app(
458 WithMiddleware::new(ArcApp(Arc::clone(&app)))
459 .wrap(BearerAuthMiddleware { token: Arc::new(token) }),
460 )
461 }
462 }
463 #[cfg(feature = "auth")]
464 AuthConfig::Jwt { secret_env } => {
465 let secret = std::env::var(secret_env).unwrap_or_default();
466 if secret.is_empty() {
467 deny(format!("JWT auth: env var '{secret_env}' is unset or empty"))
468 } else {
469 arc_app(
470 WithMiddleware::new(ArcApp(Arc::clone(&app)))
471 .wrap(crate::auth::JwtLayer::new(secret)),
472 )
473 }
474 }
475 #[cfg(not(feature = "auth"))]
476 AuthConfig::Jwt { .. } => {
477 deny("JWT auth requires the 'auth' feature, which this build doesn't include".to_string())
478 }
479 #[cfg(feature = "auth")]
480 AuthConfig::Basic { htpasswd_file } => {
481 match crate::auth::BasicAuthLayer::from_htpasswd_file(htpasswd_file) {
482 Ok(layer) => arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer)),
483 Err(e) => deny(format!("basic auth: {e}")),
484 }
485 }
486 #[cfg(not(feature = "auth"))]
487 AuthConfig::Basic { .. } => {
488 deny("basic auth requires the 'auth' feature, which this build doesn't include".to_string())
489 }
490 };
491 }
492
493 if let Some(ref rl_cfg) = mw.rate_limit {
495 let limiter = Arc::new(crate::rate_limit::RateLimiter::new(
496 rl_cfg.max_requests,
497 rl_cfg.window_secs,
498 ));
499 app = arc_app(
500 WithMiddleware::new(ArcApp(Arc::clone(&app)))
501 .wrap(PerRouteRateLimit(limiter)),
502 );
503 }
504
505 if !mw.ip_allow.is_empty() {
507 let filter = crate::ip_filter::IpFilter::allow(mw.ip_allow.iter().map(|s| s.as_str()));
508 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(filter));
509 } else if !mw.ip_deny.is_empty() {
510 let filter = crate::ip_filter::IpFilter::deny(mw.ip_deny.iter().map(|s| s.as_str()));
511 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(filter));
512 }
513
514 if let Some(timeout_ms) = mw.timeout_ms {
517 app = arc_app(crate::timeout::TimeoutLayer::from_arc(
518 app,
519 std::time::Duration::from_millis(timeout_ms),
520 ));
521 }
522
523 if let Some(max_body_size) = mw.max_body_size {
526 app = arc_app(
527 WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(PerRouteMaxBodySize(max_body_size)),
528 );
529 }
530
531 app
532}
533
534fn apply_request_rewrite_rule(
535 layer: crate::rewrite::RewriteLayer,
536 rule: &crate::proxy_config::RewriteRuleConfig,
537) -> crate::rewrite::RewriteLayer {
538 match rule.type_.as_str() {
539 "header_set" => {
540 if let (Some(name), Some(value)) = (&rule.name, &rule.value) {
541 return layer.request_header_set(name, value);
542 }
543 }
544 "header_remove" => {
545 if let Some(name) = &rule.name {
546 return layer.request_header_remove(name);
547 }
548 }
549 "uri_set" => {
550 if let Some(value) = &rule.value {
551 return layer.request_uri_set(value);
552 }
553 }
554 "uri_strip_prefix" | "strip_prefix" => {
555 if let Some(prefix) = rule.prefix.as_ref().or(rule.value.as_ref()) {
556 return layer.request_uri_strip_prefix(prefix);
557 }
558 }
559 "uri_add_prefix" | "add_prefix" => {
560 if let Some(prefix) = rule.prefix.as_ref().or(rule.value.as_ref()) {
561 return layer.request_uri_add_prefix(prefix);
562 }
563 }
564 _ => {}
565 }
566 layer
567}
568
569fn apply_response_rewrite_rule(
570 layer: crate::rewrite::RewriteLayer,
571 rule: &crate::proxy_config::RewriteRuleConfig,
572) -> crate::rewrite::RewriteLayer {
573 match rule.type_.as_str() {
574 "header_set" => {
575 if let (Some(name), Some(value)) = (&rule.name, &rule.value) {
576 return layer.response_header_set(name, value);
577 }
578 }
579 "header_remove" => {
580 if let Some(name) = &rule.name {
581 return layer.response_header_remove(name);
582 }
583 }
584 "status" => {
585 if let (Some(code), Some(reason)) = (&rule.code, &rule.reason) {
586 return layer.response_status(*code as i16, reason);
587 }
588 }
589 "body_replace" => {
590 if let (Some(from), Some(to)) = (&rule.from, &rule.to) {
591 return layer.response_body_replace(from, to);
592 }
593 }
594 _ => {}
595 }
596 layer
597}
598
599pub(crate) struct ArcApp(pub(crate) Arc<dyn crate::application::Application + Send + Sync>);
604
605impl crate::application::Application for ArcApp {
606 fn execute(
607 &self,
608 request: &crate::request::Request,
609 conn: &crate::server::ConnectionInfo,
610 ) -> Result<crate::response::Response, String> {
611 self.0.execute(request, conn)
612 }
613
614 fn streams_body(&self, request: &crate::request::Request, conn: &crate::server::ConnectionInfo) -> bool {
615 self.0.streams_body(request, conn)
616 }
617}
618
619impl Clone for ArcApp {
620 fn clone(&self) -> Self {
621 ArcApp(Arc::clone(&self.0))
622 }
623}
624
625pub(crate) fn config_authorizer(config: &ProxyConfig) -> Option<Result<crate::authz::Authorizer, String>> {
628 config_authorizer_with(config, None)
629}
630
631pub(crate) fn config_authorizer_with(
634 config: &ProxyConfig,
635 check_store: Option<Arc<dyn crate::authz::TupleStore>>,
636) -> Option<Result<crate::authz::Authorizer, String>> {
637 let authz = config.authz.as_ref()?;
638 Some((|| {
639 let mut schema = crate::authz::Schema::new();
640 for (kind, relations) in &authz.types {
641 schema = if relations.is_empty() { schema.object_type(kind) } else { Ok(schema) }.map_err(|e| e.to_string())?;
642 for (relation, definition) in relations {
643 schema = schema.relation(kind, relation, definition).map_err(|e| format!("type {kind}, relation {relation}: {e}"))?;
644 }
645 }
646 let store = match (authz.store.as_str(), check_store) {
647 ("" | "memory" | "sql", Some(store)) => store,
648 ("" | "memory", None) => crate::authz::config::memory_store(),
649 #[cfg(any(feature = "model-sqlite", feature = "model-postgres", feature = "model-mysql"))]
650 ("sql", None) => crate::authz::sql::config_store()?,
651 #[cfg(not(any(feature = "model-sqlite", feature = "model-postgres", feature = "model-mysql")))]
652 ("sql", None) => return Err("store = \"sql\" needs a build with a model-sqlite / model-postgres / model-mysql feature".to_string()),
653 (other, _) => return Err(format!("store = \"{other}\" isn't supported (memory or sql)")),
654 };
655 let authorizer = crate::authz::Authorizer::with_store(schema, store).map_err(|e| e.to_string())?;
656 for tuple in &authz.tuples {
657 authorizer.write_str(tuple).map_err(|e| format!("tuple {tuple:?}: {e}"))?;
658 }
659 Ok(authorizer)
660 })())
661}