witchcraft_server/
witchcraft.rs1use crate::blocking::conjure::ConjureBlockingEndpoint;
15use crate::blocking::pool::ThreadPool;
16use crate::debug::DiagnosticRegistry;
17use crate::endpoint::conjure::ConjureEndpoint;
18use crate::endpoint::extended_path::ExtendedPathEndpoint;
19use crate::endpoint::WitchcraftEndpoint;
20use crate::health::HealthCheckRegistry;
21use crate::readiness::ReadinessCheckRegistry;
22use crate::shutdown_hooks::ShutdownHooks;
23use crate::{blocking, RequestBody, ResponseWriter};
24use conjure_http::server::{AsyncService, BoxAsyncEndpoint, ConjureRuntime, Endpoint, Service};
25use conjure_runtime::ClientFactory;
26use futures_util::Future;
27use std::sync::Arc;
28use tokio::runtime::Handle;
29use witchcraft_metrics::MetricRegistry;
30use witchcraft_server_config::install::InstallConfig;
31
32pub struct Witchcraft {
34 pub(crate) metrics: Arc<MetricRegistry>,
35 pub(crate) health_checks: Arc<HealthCheckRegistry>,
36 pub(crate) readiness_checks: Arc<ReadinessCheckRegistry>,
37 pub(crate) diagnostics: Arc<DiagnosticRegistry>,
38 pub(crate) client_factory: ClientFactory,
39 pub(crate) handle: Handle,
40 pub(crate) install_config: InstallConfig,
41 pub(crate) thread_pool: Option<Arc<ThreadPool>>,
42 pub(crate) thread_prefix: Option<String>,
43 pub(crate) endpoints: Vec<Box<dyn WitchcraftEndpoint + Sync + Send>>,
44 pub(crate) shutdown_hooks: ShutdownHooks,
45 pub(crate) conjure_runtime: Arc<ConjureRuntime>,
46}
47
48impl Witchcraft {
49 #[inline]
51 pub fn metrics(&self) -> &Arc<MetricRegistry> {
52 &self.metrics
53 }
54
55 #[inline]
57 pub fn health_checks(&self) -> &Arc<HealthCheckRegistry> {
58 &self.health_checks
59 }
60
61 #[inline]
63 pub fn readiness_checks(&self) -> &Arc<ReadinessCheckRegistry> {
64 &self.readiness_checks
65 }
66
67 #[inline]
69 pub fn client_factory(&self) -> &ClientFactory {
70 &self.client_factory
71 }
72
73 #[inline]
75 pub fn diagnostics(&self) -> &Arc<DiagnosticRegistry> {
76 &self.diagnostics
77 }
78
79 #[inline]
81 pub fn handle(&self) -> &Handle {
82 &self.handle
83 }
84
85 pub fn app<T>(&mut self, service: T)
87 where
88 T: AsyncService<RequestBody, ResponseWriter>,
89 {
90 self.endpoints(None, service.endpoints(&self.conjure_runtime), true)
91 }
92
93 pub fn api<T>(&mut self, service: T)
95 where
96 T: AsyncService<RequestBody, ResponseWriter>,
97 {
98 self.endpoints(Some("/api"), service.endpoints(&self.conjure_runtime), true)
99 }
100
101 pub(crate) fn endpoints(
102 &mut self,
103 prefix: Option<&str>,
104 endpoints: Vec<BoxAsyncEndpoint<'static, RequestBody, ResponseWriter>>,
105 track_metrics: bool,
106 ) {
107 let metrics = if track_metrics {
108 Some(&*self.metrics)
109 } else {
110 None
111 };
112
113 self.endpoints.extend(
114 endpoints
115 .into_iter()
116 .map(|e| Box::new(ConjureEndpoint::new(metrics, e)))
117 .map(|e| extend_path(e, self.install_config.context_path(), prefix)),
118 )
119 }
120
121 pub fn blocking_app<T>(&mut self, service: T)
123 where
124 T: Service<blocking::RequestBody, blocking::ResponseWriter>,
125 {
126 self.blocking_endpoints(None, service.endpoints(&self.conjure_runtime))
127 }
128
129 pub fn blocking_api<T>(&mut self, service: T)
131 where
132 T: Service<blocking::RequestBody, blocking::ResponseWriter>,
133 {
134 self.blocking_endpoints(Some("/api"), service.endpoints(&self.conjure_runtime))
135 }
136
137 fn blocking_endpoints(
138 &mut self,
139 prefix: Option<&str>,
140 endpoints: Vec<
141 Box<dyn Endpoint<blocking::RequestBody, blocking::ResponseWriter> + Sync + Send>,
142 >,
143 ) {
144 let thread_pool = self.thread_pool.get_or_insert_with(|| {
145 Arc::new(ThreadPool::new(
146 &self.install_config,
147 &self.metrics,
148 self.thread_prefix.clone(),
149 ))
150 });
151
152 self.endpoints.extend(
153 endpoints
154 .into_iter()
155 .map(|e| Box::new(ConjureBlockingEndpoint::new(&self.metrics, thread_pool, e)))
156 .map(|e| extend_path(e, self.install_config.context_path(), prefix)),
157 )
158 }
159
160 pub fn on_shutdown<F>(&mut self, future: F)
164 where
165 F: Future<Output = ()> + 'static + Send,
166 {
167 self.shutdown_hooks.push(future)
168 }
169}
170
171fn extend_path(
172 endpoint: Box<dyn WitchcraftEndpoint + Sync + Send>,
173 context_path: &str,
174 prefix: Option<&str>,
175) -> Box<dyn WitchcraftEndpoint + Sync + Send> {
176 let context_path = if context_path == "/" {
177 ""
178 } else {
179 context_path
180 };
181 let prefix = format!("{context_path}{}", prefix.unwrap_or(""));
182
183 if prefix.is_empty() {
184 endpoint
185 } else {
186 Box::new(ExtendedPathEndpoint::new(endpoint, &prefix))
187 }
188}