rust_ef/db_context/
options.rs1use crate::error::EFResult;
5use crate::provider::IDatabaseProvider;
6use std::sync::Arc;
7
8#[derive(Clone)]
9pub struct DbContextOptions {
10 pub(crate) connection_string: String,
11 pub(crate) provider_tag: Option<String>,
12 #[allow(clippy::type_complexity)]
13 pub(crate) provider_factory:
14 Option<Arc<dyn Fn(&str) -> EFResult<Arc<dyn IDatabaseProvider>> + Send + Sync>>,
15 pub(crate) provider_cache: Arc<std::sync::Mutex<Option<Arc<dyn IDatabaseProvider>>>>,
22 pub(crate) interceptors: Vec<Arc<dyn crate::interceptor::ISaveChangesInterceptor>>,
23 pub(crate) lazy_loading_enabled: bool,
29 pub(crate) context_key: Option<String>,
30 pub(crate) metadata_cache: Arc<crate::metadata_cache::MetadataCache>,
36 #[cfg(feature = "tracing")]
39 pub(crate) slow_query_threshold: Option<std::time::Duration>,
40}
41
42impl std::fmt::Debug for DbContextOptions {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 f.debug_struct("DbContextOptions")
45 .field(
46 "connection_string",
47 &redact_connection_string(&self.connection_string),
48 )
49 .field("provider_tag", &self.provider_tag)
50 .finish()
51 }
52}
53
54fn redact_connection_string(cs: &str) -> String {
59 if let Some(scheme_end) = cs.find("://") {
61 let (scheme, rest) = cs.split_at(scheme_end + 3);
62 if let Some(at) = rest.find('@') {
63 let (userinfo, host_and_rest) = rest.split_at(at);
64 let redacted_user = match userinfo.find(':') {
65 Some(colon) => &userinfo[..colon],
66 None => userinfo,
67 };
68 return format!("{}{}***@{}", scheme, redacted_user, &host_and_rest[1..]);
69 }
70 return cs.to_string();
71 }
72 if cs.contains('=') {
74 return cs
75 .split(';')
76 .map(|pair| {
77 let eq = match pair.find('=') {
78 Some(e) => e,
79 None => return pair.to_string(),
80 };
81 let key = pair[..eq].trim().to_lowercase();
82 if key.contains("password") || key.contains("pwd") {
83 format!("{}=***", &pair[..eq])
84 } else {
85 pair.to_string()
86 }
87 })
88 .collect::<Vec<_>>()
89 .join(";");
90 }
91 cs.to_string()
92}
93
94impl DbContextOptions {
95 pub fn connection_string(&self) -> &str {
96 &self.connection_string
97 }
98 pub fn provider_tag(&self) -> Option<&str> {
99 self.provider_tag.as_deref()
100 }
101 pub fn lazy_loading_enabled(&self) -> bool {
102 self.lazy_loading_enabled
103 }
104 pub fn context_key(&self) -> Option<&str> {
105 self.context_key.as_deref()
106 }
107 pub fn create_provider(&self) -> EFResult<Arc<dyn IDatabaseProvider>> {
108 let mut guard = self
112 .provider_cache
113 .lock()
114 .unwrap_or_else(|p| p.into_inner());
115 if let Some(provider) = guard.as_ref() {
116 return Ok(Arc::clone(provider));
117 }
118 let factory = self.provider_factory.as_ref().ok_or_else(|| {
119 crate::error::EFError::configuration(
120 "No provider configured. Call use_sqlite / use_postgres / use_mysql first.",
121 )
122 })?;
123 let provider = factory(self.connection_string())?;
124 #[cfg(feature = "tracing")]
125 if let Some(threshold) = self.slow_query_threshold {
126 provider.set_slow_query_threshold(threshold);
127 }
128 *guard = Some(Arc::clone(&provider));
129 Ok(provider)
130 }
131}
132
133#[allow(clippy::derivable_impls)]
134impl Default for DbContextOptions {
135 fn default() -> Self {
136 Self {
137 connection_string: String::new(),
138 provider_tag: None,
139 provider_factory: None,
140 provider_cache: Arc::new(std::sync::Mutex::new(None)),
141 interceptors: Vec::new(),
142 lazy_loading_enabled: false,
143 context_key: None,
144 metadata_cache: Arc::new(crate::metadata_cache::MetadataCache::new()),
145 #[cfg(feature = "tracing")]
146 slow_query_threshold: None,
147 }
148 }
149}
150
151pub struct DbContextOptionsBuilder {
152 inner: DbContextOptions,
153}
154
155impl DbContextOptionsBuilder {
156 pub fn new() -> Self {
157 Self {
158 inner: DbContextOptions::default(),
159 }
160 }
161 pub fn connection_string(&mut self, cs: impl Into<String>) -> &mut Self {
162 self.inner.connection_string = cs.into();
163 self
164 }
165 pub fn set_provider(&mut self, tag: &str, cs: impl Into<String>) -> &mut Self {
166 self.inner.provider_tag = Some(tag.to_string());
167 self.inner.connection_string = cs.into();
168 self
169 }
170 #[allow(clippy::type_complexity)]
171 pub fn set_provider_factory(
172 &mut self,
173 tag: &str,
174 cs: impl Into<String>,
175 factory: Arc<dyn Fn(&str) -> EFResult<Arc<dyn IDatabaseProvider>> + Send + Sync>,
176 ) -> &mut Self {
177 self.inner.provider_tag = Some(tag.to_string());
178 self.inner.connection_string = cs.into();
179 self.inner.provider_factory = Some(factory);
180 self
181 }
182 pub fn add_interceptor(
196 &mut self,
197 interceptor: impl crate::interceptor::ISaveChangesInterceptor + 'static,
198 ) -> &mut Self {
199 self.inner.interceptors.push(Arc::new(interceptor));
200 self
201 }
202
203 pub fn use_lazy_loading(&mut self, enabled: bool) -> &mut Self {
221 self.inner.lazy_loading_enabled = enabled;
222 self
223 }
224
225 pub fn context_key(&mut self, key: impl Into<String>) -> &mut Self {
230 self.inner.context_key = Some(key.into());
231 self
232 }
233
234 #[cfg(feature = "tracing")]
239 pub fn slow_query_threshold(&mut self, threshold: std::time::Duration) -> &mut Self {
240 self.inner.slow_query_threshold = Some(threshold);
241 self
242 }
243
244 pub fn build(self) -> DbContextOptions {
245 self.inner
246 }
247}
248
249impl Default for DbContextOptionsBuilder {
250 fn default() -> Self {
251 Self::new()
252 }
253}