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) shared_entity_model: bool,
34 pub(crate) metadata_cache: Arc<crate::metadata_cache::MetadataCache>,
40 #[cfg(feature = "tracing")]
43 pub(crate) slow_query_threshold: Option<std::time::Duration>,
44}
45
46impl std::fmt::Debug for DbContextOptions {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 f.debug_struct("DbContextOptions")
49 .field(
50 "connection_string",
51 &redact_connection_string(&self.connection_string),
52 )
53 .field("provider_tag", &self.provider_tag)
54 .finish()
55 }
56}
57
58fn redact_connection_string(cs: &str) -> String {
63 if let Some(scheme_end) = cs.find("://") {
65 let (scheme, rest) = cs.split_at(scheme_end + 3);
66 if let Some(at) = rest.find('@') {
67 let (userinfo, host_and_rest) = rest.split_at(at);
68 let redacted_user = match userinfo.find(':') {
69 Some(colon) => &userinfo[..colon],
70 None => userinfo,
71 };
72 return format!("{}{}***@{}", scheme, redacted_user, &host_and_rest[1..]);
73 }
74 return cs.to_string();
75 }
76 if cs.contains('=') {
78 return cs
79 .split(';')
80 .map(|pair| {
81 let eq = match pair.find('=') {
82 Some(e) => e,
83 None => return pair.to_string(),
84 };
85 let key = pair[..eq].trim().to_lowercase();
86 if key.contains("password") || key.contains("pwd") {
87 format!("{}=***", &pair[..eq])
88 } else {
89 pair.to_string()
90 }
91 })
92 .collect::<Vec<_>>()
93 .join(";");
94 }
95 cs.to_string()
96}
97
98impl DbContextOptions {
99 pub fn connection_string(&self) -> &str {
100 &self.connection_string
101 }
102 pub fn provider_tag(&self) -> Option<&str> {
103 self.provider_tag.as_deref()
104 }
105 pub fn lazy_loading_enabled(&self) -> bool {
106 self.lazy_loading_enabled
107 }
108 pub fn context_key(&self) -> Option<&str> {
109 self.context_key.as_deref()
110 }
111 pub fn create_provider(&self) -> EFResult<Arc<dyn IDatabaseProvider>> {
112 let mut guard = self
116 .provider_cache
117 .lock()
118 .unwrap_or_else(|p| p.into_inner());
119 if let Some(provider) = guard.as_ref() {
120 return Ok(Arc::clone(provider));
121 }
122 let factory = self.provider_factory.as_ref().ok_or_else(|| {
123 crate::error::EFError::configuration(
124 "No provider configured. Call use_sqlite / use_postgres / use_mysql first.",
125 )
126 })?;
127 let provider = factory(self.connection_string())?;
128 #[cfg(feature = "tracing")]
129 if let Some(threshold) = self.slow_query_threshold {
130 provider.set_slow_query_threshold(threshold);
131 }
132 *guard = Some(Arc::clone(&provider));
133 Ok(provider)
134 }
135}
136
137#[allow(clippy::derivable_impls)]
138impl Default for DbContextOptions {
139 fn default() -> Self {
140 Self {
141 connection_string: String::new(),
142 provider_tag: None,
143 provider_factory: None,
144 provider_cache: Arc::new(std::sync::Mutex::new(None)),
145 interceptors: Vec::new(),
146 lazy_loading_enabled: false,
147 context_key: None,
148 shared_entity_model: false,
149 metadata_cache: Arc::new(crate::metadata_cache::MetadataCache::new()),
150 #[cfg(feature = "tracing")]
151 slow_query_threshold: None,
152 }
153 }
154}
155
156pub struct DbContextOptionsBuilder {
157 inner: DbContextOptions,
158}
159
160impl DbContextOptionsBuilder {
161 pub fn new() -> Self {
162 Self {
163 inner: DbContextOptions::default(),
164 }
165 }
166 pub fn connection_string(&mut self, cs: impl Into<String>) -> &mut Self {
167 self.inner.connection_string = cs.into();
168 self
169 }
170 pub fn set_provider(&mut self, tag: &str, cs: impl Into<String>) -> &mut Self {
171 self.inner.provider_tag = Some(tag.to_string());
172 self.inner.connection_string = cs.into();
173 self
174 }
175 #[allow(clippy::type_complexity)]
176 pub fn set_provider_factory(
177 &mut self,
178 tag: &str,
179 cs: impl Into<String>,
180 factory: Arc<dyn Fn(&str) -> EFResult<Arc<dyn IDatabaseProvider>> + Send + Sync>,
181 ) -> &mut Self {
182 self.inner.provider_tag = Some(tag.to_string());
183 self.inner.connection_string = cs.into();
184 self.inner.provider_factory = Some(factory);
185 self
186 }
187 pub fn add_interceptor(
201 &mut self,
202 interceptor: impl crate::interceptor::ISaveChangesInterceptor + 'static,
203 ) -> &mut Self {
204 self.inner.interceptors.push(Arc::new(interceptor));
205 self
206 }
207
208 pub fn use_lazy_loading(&mut self, enabled: bool) -> &mut Self {
226 self.inner.lazy_loading_enabled = enabled;
227 self
228 }
229
230 pub fn context_key(&mut self, key: impl Into<String>) -> &mut Self {
235 self.inner.context_key = Some(key.into());
236 self
237 }
238
239 pub fn use_default_entity_model(&mut self) -> &mut Self {
252 self.inner.shared_entity_model = true;
253 self.inner.context_key = None;
254 self
255 }
256
257 pub(crate) fn uses_default_entity_model(&self) -> bool {
258 self.inner.shared_entity_model
259 }
260
261 #[cfg(feature = "tracing")]
266 pub fn slow_query_threshold(&mut self, threshold: std::time::Duration) -> &mut Self {
267 self.inner.slow_query_threshold = Some(threshold);
268 self
269 }
270
271 pub fn build(self) -> DbContextOptions {
272 self.inner
273 }
274}
275
276impl Default for DbContextOptionsBuilder {
277 fn default() -> Self {
278 Self::new()
279 }
280}