use crate::error::EFResult;
use crate::provider::IDatabaseProvider;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
pub type AsyncPreflightFuture = Pin<Box<dyn Future<Output = EFResult<()>> + Send>>;
pub type AsyncPreflight = Arc<dyn Fn(Arc<DbContextOptions>) -> AsyncPreflightFuture + Send + Sync>;
#[derive(Clone)]
pub struct DbContextOptions {
pub(crate) connection_string: String,
pub(crate) provider_tag: Option<String>,
#[allow(clippy::type_complexity)]
pub(crate) provider_factory:
Option<Arc<dyn Fn(&str) -> EFResult<Arc<dyn IDatabaseProvider>> + Send + Sync>>,
pub(crate) provider_cache: Arc<std::sync::Mutex<Option<Arc<dyn IDatabaseProvider>>>>,
pub(crate) interceptors: Vec<Arc<dyn crate::interceptor::ISaveChangesInterceptor>>,
pub(crate) lazy_loading_enabled: bool,
pub(crate) context_key: Option<String>,
pub(crate) shared_entity_model: bool,
pub(crate) async_preflight: Option<AsyncPreflight>,
pub(crate) metadata_cache: Arc<crate::metadata_cache::MetadataCache>,
#[cfg(feature = "tracing")]
pub(crate) slow_query_threshold: Option<std::time::Duration>,
}
impl std::fmt::Debug for DbContextOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DbContextOptions")
.field(
"connection_string",
&redact_connection_string(&self.connection_string),
)
.field("provider_tag", &self.provider_tag)
.finish()
}
}
fn redact_connection_string(cs: &str) -> String {
if let Some(scheme_end) = cs.find("://") {
let (scheme, rest) = cs.split_at(scheme_end + 3);
if let Some(at) = rest.find('@') {
let (userinfo, host_and_rest) = rest.split_at(at);
let redacted_user = match userinfo.find(':') {
Some(colon) => &userinfo[..colon],
None => userinfo,
};
return format!("{}{}***@{}", scheme, redacted_user, &host_and_rest[1..]);
}
return cs.to_string();
}
if cs.contains('=') {
return cs
.split(';')
.map(|pair| {
let eq = match pair.find('=') {
Some(e) => e,
None => return pair.to_string(),
};
let key = pair[..eq].trim().to_lowercase();
if key.contains("password") || key.contains("pwd") {
format!("{}=***", &pair[..eq])
} else {
pair.to_string()
}
})
.collect::<Vec<_>>()
.join(";");
}
cs.to_string()
}
impl DbContextOptions {
pub fn connection_string(&self) -> &str {
&self.connection_string
}
pub fn provider_tag(&self) -> Option<&str> {
self.provider_tag.as_deref()
}
pub fn lazy_loading_enabled(&self) -> bool {
self.lazy_loading_enabled
}
pub fn context_key(&self) -> Option<&str> {
self.context_key.as_deref()
}
pub fn create_provider(&self) -> EFResult<Arc<dyn IDatabaseProvider>> {
let mut guard = self
.provider_cache
.lock()
.unwrap_or_else(|p| p.into_inner());
if let Some(provider) = guard.as_ref() {
return Ok(Arc::clone(provider));
}
let factory = self.provider_factory.as_ref().ok_or_else(|| {
crate::error::EFError::configuration(
"No provider configured. Call use_sqlite / use_postgres / use_mysql first.",
)
})?;
let provider = factory(self.connection_string())?;
#[cfg(feature = "tracing")]
if let Some(threshold) = self.slow_query_threshold {
provider.set_slow_query_threshold(threshold);
}
*guard = Some(Arc::clone(&provider));
Ok(provider)
}
}
#[allow(clippy::derivable_impls)]
impl Default for DbContextOptions {
fn default() -> Self {
Self {
connection_string: String::new(),
provider_tag: None,
provider_factory: None,
provider_cache: Arc::new(std::sync::Mutex::new(None)),
interceptors: Vec::new(),
lazy_loading_enabled: false,
context_key: None,
shared_entity_model: false,
async_preflight: None,
metadata_cache: Arc::new(crate::metadata_cache::MetadataCache::new()),
#[cfg(feature = "tracing")]
slow_query_threshold: None,
}
}
}
pub struct DbContextOptionsBuilder {
inner: DbContextOptions,
}
impl DbContextOptionsBuilder {
pub fn new() -> Self {
Self {
inner: DbContextOptions::default(),
}
}
pub fn connection_string(&mut self, cs: impl Into<String>) -> &mut Self {
self.inner.connection_string = cs.into();
self
}
pub fn set_provider(&mut self, tag: &str, cs: impl Into<String>) -> &mut Self {
self.inner.provider_tag = Some(tag.to_string());
self.inner.connection_string = cs.into();
self
}
#[allow(clippy::type_complexity)]
pub fn set_provider_factory(
&mut self,
tag: &str,
cs: impl Into<String>,
factory: Arc<dyn Fn(&str) -> EFResult<Arc<dyn IDatabaseProvider>> + Send + Sync>,
) -> &mut Self {
self.inner.provider_tag = Some(tag.to_string());
self.inner.connection_string = cs.into();
self.inner.provider_factory = Some(factory);
self
}
pub fn add_interceptor(
&mut self,
interceptor: impl crate::interceptor::ISaveChangesInterceptor + 'static,
) -> &mut Self {
self.inner.interceptors.push(Arc::new(interceptor));
self
}
pub fn use_lazy_loading(&mut self, enabled: bool) -> &mut Self {
self.inner.lazy_loading_enabled = enabled;
self
}
pub fn async_preflight(
&mut self,
preflight: impl Fn(Arc<DbContextOptions>) -> AsyncPreflightFuture + Send + Sync + 'static,
) -> &mut Self {
self.inner.async_preflight = Some(Arc::new(preflight));
self
}
pub fn context_key(&mut self, key: impl Into<String>) -> &mut Self {
self.inner.context_key = Some(key.into());
self
}
pub fn use_default_entity_model(&mut self) -> &mut Self {
self.inner.shared_entity_model = true;
self.inner.context_key = None;
self
}
pub(crate) fn uses_default_entity_model(&self) -> bool {
self.inner.shared_entity_model
}
#[cfg(feature = "tracing")]
pub fn slow_query_threshold(&mut self, threshold: std::time::Duration) -> &mut Self {
self.inner.slow_query_threshold = Some(threshold);
self
}
pub fn build(self) -> DbContextOptions {
self.inner
}
}
impl Default for DbContextOptionsBuilder {
fn default() -> Self {
Self::new()
}
}