Directory implementation - part 2
This commit is contained in:
63
crates/directory/src/config.rs
Normal file
63
crates/directory/src/config.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use bb8::{ManageConnection, Pool};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use utils::config::Config;
|
||||
|
||||
use ahash::AHashMap;
|
||||
|
||||
use crate::{
|
||||
imap::ImapDirectory, ldap::LdapDirectory, smtp::SmtpDirectory, sql::SqlDirectory, Directory,
|
||||
};
|
||||
|
||||
pub trait ConfigDirectory {
|
||||
fn parse_directory(&self) -> utils::config::Result<AHashMap<String, Arc<dyn Directory>>>;
|
||||
}
|
||||
|
||||
impl ConfigDirectory for Config {
|
||||
fn parse_directory(&self) -> utils::config::Result<AHashMap<String, Arc<dyn Directory>>> {
|
||||
let mut directories = AHashMap::new();
|
||||
for id in self.sub_keys("directory") {
|
||||
directories.insert(
|
||||
id.to_string(),
|
||||
match self.value_require(("directory", id, "protocol"))? {
|
||||
"ldap" => LdapDirectory::from_config(self, ("directory", id))?,
|
||||
"sql" => SqlDirectory::from_config(self, ("directory", id))?,
|
||||
"imap" => ImapDirectory::from_config(self, ("directory", id))?,
|
||||
"smtp" => SmtpDirectory::from_config(self, ("directory", id), false)?,
|
||||
"lmtp" => SmtpDirectory::from_config(self, ("directory", id), true)?,
|
||||
unknown => {
|
||||
return Err(format!("Unknown directory type: {unknown:?}"));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok(directories)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build_pool<M: ManageConnection>(
|
||||
config: &Config,
|
||||
prefix: &str,
|
||||
manager: M,
|
||||
) -> utils::config::Result<Pool<M>> {
|
||||
Ok(Pool::builder()
|
||||
.min_idle(
|
||||
config
|
||||
.property((prefix, "pool.min-connections"))?
|
||||
.and_then(|v| if v > 0 { Some(v) } else { None }),
|
||||
)
|
||||
.max_size(config.property_or_static((prefix, "pool.max-connections"), "10")?)
|
||||
.max_lifetime(
|
||||
config
|
||||
.property_or_static::<Duration>((prefix, "pool.max-lifetime"), "30m")?
|
||||
.into(),
|
||||
)
|
||||
.idle_timeout(
|
||||
config
|
||||
.property_or_static::<Duration>((prefix, "pool.idle-timeout"), "10m")?
|
||||
.into(),
|
||||
)
|
||||
.connection_timeout(config.property_or_static((prefix, "pool.connect-timeout"), "30s")?)
|
||||
.test_on_check_out(true)
|
||||
.build_unchecked(manager))
|
||||
}
|
||||
@@ -166,3 +166,33 @@ impl<T: AsyncRead + AsyncWrite + Unpin> ImapClient<T> {
|
||||
self.stream.flush().await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use mail_send::smtp::tls::build_tls_connector;
|
||||
use smtp_proto::{AUTH_OAUTHBEARER, AUTH_PLAIN, AUTH_XOAUTH, AUTH_XOAUTH2};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::imap::ImapClient;
|
||||
|
||||
#[ignore]
|
||||
#[tokio::test]
|
||||
async fn imap_auth() {
|
||||
let connector = build_tls_connector(false);
|
||||
|
||||
let mut client = ImapClient::connect(
|
||||
"imap.gmail.com:993",
|
||||
Duration::from_secs(5),
|
||||
&connector,
|
||||
"imap.gmail.com",
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
AUTH_PLAIN | AUTH_XOAUTH | AUTH_XOAUTH2 | AUTH_OAUTHBEARER,
|
||||
client.authentication_mechanisms().await.unwrap()
|
||||
);
|
||||
client.logout().await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
36
crates/directory/src/imap/config.rs
Normal file
36
crates/directory/src/imap/config.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use mail_send::smtp::tls::build_tls_connector;
|
||||
use utils::config::{utils::AsKey, Config};
|
||||
|
||||
use crate::{config::build_pool, imap::ImapConnectionManager, Directory};
|
||||
|
||||
use super::ImapDirectory;
|
||||
|
||||
impl ImapDirectory {
|
||||
pub fn from_config(
|
||||
config: &Config,
|
||||
prefix: impl AsKey,
|
||||
) -> utils::config::Result<Arc<dyn Directory>> {
|
||||
let prefix = prefix.as_key();
|
||||
let address = config.value_require((&prefix, "address"))?;
|
||||
let tls_implicit: bool = config.property_or_static((&prefix, "tls.implicit"), "false")?;
|
||||
let port: u16 = config
|
||||
.property_or_static((&prefix, "port"), if tls_implicit { "443" } else { "143" })?;
|
||||
|
||||
let manager = ImapConnectionManager {
|
||||
addr: format!("{address}:{port}"),
|
||||
timeout: config.property_or_static((&prefix, "timeout"), "30s")?,
|
||||
tls_connector: build_tls_connector(
|
||||
config.property_or_static((&prefix, "tls.allow-invalid-certs"), "false")?,
|
||||
),
|
||||
tls_hostname: address.to_string(),
|
||||
tls_implicit,
|
||||
mechanisms: 0.into(),
|
||||
};
|
||||
|
||||
Ok(Arc::new(ImapDirectory {
|
||||
pool: build_pool(config, &prefix, manager)?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,10 @@ impl Directory for ImapDirectory {
|
||||
};
|
||||
|
||||
match client.authenticate(mechanism, credentials).await {
|
||||
Ok(_) => Ok(Some(Principal::default())),
|
||||
Ok(_) => {
|
||||
client.is_valid = false;
|
||||
Ok(Some(Principal::default()))
|
||||
}
|
||||
Err(err) => match &err {
|
||||
ImapError::AuthenticationFailed => Ok(None),
|
||||
_ => Err(err.into()),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
pub mod lookup;
|
||||
pub mod pool;
|
||||
pub mod tls;
|
||||
@@ -25,6 +26,7 @@ pub struct ImapConnectionManager {
|
||||
pub struct ImapClient<T: AsyncRead + AsyncWrite> {
|
||||
stream: T,
|
||||
mechanisms: u64,
|
||||
is_valid: bool,
|
||||
timeout: Duration,
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,6 @@ impl ManageConnection for ImapConnectionManager {
|
||||
|
||||
/// Synchronously determine if the connection is no longer usable, if possible.
|
||||
fn has_broken(&self, conn: &mut Self::Connection) -> bool {
|
||||
false
|
||||
!conn.is_valid
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ impl ImapClient<TcpStream> {
|
||||
.await?,
|
||||
timeout: self.timeout,
|
||||
mechanisms: self.mechanisms,
|
||||
is_valid: true,
|
||||
})
|
||||
})
|
||||
.await
|
||||
@@ -65,6 +66,7 @@ impl ImapClient<TlsStream<TcpStream>> {
|
||||
stream,
|
||||
timeout,
|
||||
mechanisms: 0,
|
||||
is_valid: true,
|
||||
}),
|
||||
Err(err) => Err(ImapError::Io(err)),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use ldap3::LdapConnSettings;
|
||||
use utils::config::{utils::AsKey, Config};
|
||||
|
||||
use crate::{config::build_pool, Directory};
|
||||
|
||||
use super::{Bind, LdapConnectionManager, LdapDirectory, LdapFilter, LdapMappings};
|
||||
|
||||
impl LdapDirectory {
|
||||
pub fn from_config(
|
||||
config: &Config,
|
||||
prefix: impl AsKey,
|
||||
) -> utils::config::Result<Arc<dyn Directory>> {
|
||||
let prefix = prefix.as_key();
|
||||
let bind_dn = if let Some(dn) = config.value((&prefix, "bind.dn")) {
|
||||
Bind::new(
|
||||
dn.to_string(),
|
||||
config.value_require((&prefix, "bind.secret"))?.to_string(),
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let manager = LdapConnectionManager::new(
|
||||
config.value_require((&prefix, "address"))?.to_string(),
|
||||
LdapConnSettings::new()
|
||||
.set_conn_timeout(config.property_or_static((&prefix, "timeout"), "30s")?)
|
||||
.set_starttls(config.property_or_static((&prefix, "tls"), "false")?)
|
||||
.set_no_tls_verify(
|
||||
config.property_or_static((&prefix, "allow-invalid-certs"), "false")?,
|
||||
),
|
||||
bind_dn,
|
||||
);
|
||||
|
||||
let mut mappings = LdapMappings {
|
||||
base_dn: config.value_require((&prefix, "address"))?.to_string(),
|
||||
filter_login: LdapFilter::from_config(config, (&prefix, "filter.login"))?,
|
||||
filter_name: LdapFilter::from_config(config, (&prefix, "filter.name"))?,
|
||||
filter_email: LdapFilter::from_config(config, (&prefix, "filter.email"))?,
|
||||
filter_id: LdapFilter::from_config(config, (&prefix, "filter.id"))?,
|
||||
filter_verify: LdapFilter::from_config(config, (&prefix, "filter.verify"))?,
|
||||
filter_expand: LdapFilter::from_config(config, (&prefix, "filter.expand"))?,
|
||||
obj_user: config
|
||||
.value_require((&prefix, "object-classes.user"))?
|
||||
.to_string(),
|
||||
obj_group: config
|
||||
.value_require((&prefix, "object-classes.group"))?
|
||||
.to_string(),
|
||||
attr_name: config
|
||||
.values((&prefix, "attributes.name"))
|
||||
.map(|(_, v)| v.to_string())
|
||||
.collect(),
|
||||
attr_description: config
|
||||
.values((&prefix, "attributes.description"))
|
||||
.map(|(_, v)| v.to_string())
|
||||
.collect(),
|
||||
attr_secret: config
|
||||
.values((&prefix, "attributes.secret"))
|
||||
.map(|(_, v)| v.to_string())
|
||||
.collect(),
|
||||
attr_groups: config
|
||||
.values((&prefix, "attributes.groups"))
|
||||
.map(|(_, v)| v.to_string())
|
||||
.collect(),
|
||||
attr_id: config
|
||||
.values((&prefix, "attributes.id"))
|
||||
.map(|(_, v)| v.to_string())
|
||||
.collect(),
|
||||
attr_email_address: config
|
||||
.values((&prefix, "attributes.email"))
|
||||
.map(|(_, v)| v.to_string())
|
||||
.collect(),
|
||||
attr_quota: config
|
||||
.values((&prefix, "attributes."))
|
||||
.map(|(_, v)| v.to_string())
|
||||
.collect(),
|
||||
attrs_principal: vec!["objectClass".to_string()],
|
||||
attrs_email: config
|
||||
.values((&prefix, "attributes.email-alias"))
|
||||
.map(|(_, v)| v.to_string())
|
||||
.collect(),
|
||||
};
|
||||
|
||||
for attr in [
|
||||
&mappings.attr_id,
|
||||
&mappings.attr_name,
|
||||
&mappings.attr_description,
|
||||
&mappings.attr_secret,
|
||||
&mappings.attr_quota,
|
||||
&mappings.attr_groups,
|
||||
] {
|
||||
mappings.attrs_principal.extend(attr.iter().cloned());
|
||||
}
|
||||
|
||||
mappings
|
||||
.attrs_email
|
||||
.extend(mappings.attr_email_address.iter().cloned());
|
||||
|
||||
Ok(Arc::new(LdapDirectory {
|
||||
mappings,
|
||||
pool: build_pool(config, &prefix, manager)?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl LdapFilter {
|
||||
fn from_config(config: &Config, key: impl AsKey) -> utils::config::Result<Self> {
|
||||
if let Some(value) = config.value(key.clone()) {
|
||||
let filter = LdapFilter {
|
||||
filter: value.split('?').map(|s| s.to_string()).collect(),
|
||||
};
|
||||
if filter.filter.len() >= 2 {
|
||||
Ok(filter)
|
||||
} else {
|
||||
Err(format!(
|
||||
"Missing '?' parameter placeholder in filter {:?} with value {:?}",
|
||||
key.as_key(),
|
||||
value
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Ok(Self::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use bb8::Pool;
|
||||
use ldap3::LdapConnSettings;
|
||||
use ldap3::{ldap_escape, LdapConnSettings};
|
||||
|
||||
pub mod config;
|
||||
pub mod lookup;
|
||||
@@ -10,6 +10,7 @@ pub struct LdapDirectory {
|
||||
mappings: LdapMappings,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct LdapMappings {
|
||||
base_dn: String,
|
||||
filter_login: LdapFilter,
|
||||
@@ -31,13 +32,15 @@ pub struct LdapMappings {
|
||||
attrs_email: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct LdapFilter {
|
||||
filter: Vec<String>,
|
||||
}
|
||||
|
||||
impl LdapFilter {
|
||||
pub fn build(&self, value: &str) -> String {
|
||||
self.filter.join(value)
|
||||
let value = ldap_escape(value);
|
||||
self.filter.join(value.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,13 @@ use imap::ImapError;
|
||||
use ldap3::LdapError;
|
||||
use mail_send::Credentials;
|
||||
|
||||
pub mod config;
|
||||
pub mod imap;
|
||||
pub mod ldap;
|
||||
pub mod smtp;
|
||||
pub mod sql;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Principal {
|
||||
pub id: u32,
|
||||
pub name: String,
|
||||
@@ -29,6 +30,7 @@ pub enum Type {
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DirectoryError {
|
||||
Ldap(LdapError),
|
||||
Sql(sqlx::Error),
|
||||
@@ -39,7 +41,7 @@ pub enum DirectoryError {
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait Directory {
|
||||
pub trait Directory: Sync + Send {
|
||||
async fn authenticate(&self, credentials: &Credentials<String>) -> Result<Option<Principal>>;
|
||||
async fn principal_by_name(&self, name: &str) -> Result<Option<Principal>>;
|
||||
async fn principal_by_id(&self, id: u32) -> Result<Option<Principal>>;
|
||||
@@ -54,17 +56,11 @@ pub trait Directory {
|
||||
|
||||
pub type Result<T> = std::result::Result<T, DirectoryError>;
|
||||
|
||||
impl From<LdapError> for DirectoryError {
|
||||
fn from(error: LdapError) -> Self {
|
||||
DirectoryError::Ldap(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RunError<LdapError>> for DirectoryError {
|
||||
fn from(error: RunError<LdapError>) -> Self {
|
||||
match error {
|
||||
RunError::User(error) => DirectoryError::Ldap(error),
|
||||
RunError::TimedOut => DirectoryError::TimedOut,
|
||||
RunError::User(error) => error.into(),
|
||||
RunError::TimedOut => DirectoryError::timeout("ldap"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,8 +68,8 @@ impl From<RunError<LdapError>> for DirectoryError {
|
||||
impl From<RunError<ImapError>> for DirectoryError {
|
||||
fn from(error: RunError<ImapError>) -> Self {
|
||||
match error {
|
||||
RunError::User(error) => DirectoryError::Imap(error),
|
||||
RunError::TimedOut => DirectoryError::TimedOut,
|
||||
RunError::User(error) => error.into(),
|
||||
RunError::TimedOut => DirectoryError::timeout("imap"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,26 +77,64 @@ impl From<RunError<ImapError>> for DirectoryError {
|
||||
impl From<RunError<mail_send::Error>> for DirectoryError {
|
||||
fn from(error: RunError<mail_send::Error>) -> Self {
|
||||
match error {
|
||||
RunError::User(error) => DirectoryError::Smtp(error),
|
||||
RunError::TimedOut => DirectoryError::TimedOut,
|
||||
RunError::User(error) => error.into(),
|
||||
RunError::TimedOut => DirectoryError::timeout("smtp"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LdapError> for DirectoryError {
|
||||
fn from(error: LdapError) -> Self {
|
||||
tracing::warn!(
|
||||
context = "directory",
|
||||
event = "error",
|
||||
protocol = "ldap",
|
||||
reason = %error,
|
||||
"LDAP directory error"
|
||||
);
|
||||
|
||||
DirectoryError::Ldap(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sqlx::Error> for DirectoryError {
|
||||
fn from(error: sqlx::Error) -> Self {
|
||||
tracing::warn!(
|
||||
context = "directory",
|
||||
event = "error",
|
||||
protocol = "sql",
|
||||
reason = %error,
|
||||
"SQL directory error"
|
||||
);
|
||||
|
||||
DirectoryError::Sql(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ImapError> for DirectoryError {
|
||||
fn from(error: ImapError) -> Self {
|
||||
tracing::warn!(
|
||||
context = "directory",
|
||||
event = "error",
|
||||
protocol = "imap",
|
||||
reason = %error,
|
||||
"IMAP directory error"
|
||||
);
|
||||
|
||||
DirectoryError::Imap(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mail_send::Error> for DirectoryError {
|
||||
fn from(error: mail_send::Error) -> Self {
|
||||
tracing::warn!(
|
||||
context = "directory",
|
||||
event = "error",
|
||||
protocol = "smtp",
|
||||
reason = %error,
|
||||
"SMTP directory error"
|
||||
);
|
||||
|
||||
DirectoryError::Smtp(error)
|
||||
}
|
||||
}
|
||||
@@ -108,7 +142,7 @@ impl From<mail_send::Error> for DirectoryError {
|
||||
impl DirectoryError {
|
||||
pub fn unsupported(protocol: &str, method: &str) -> Self {
|
||||
tracing::warn!(
|
||||
context = "remote",
|
||||
context = "directory",
|
||||
event = "error",
|
||||
protocol = protocol,
|
||||
method = method,
|
||||
@@ -116,6 +150,16 @@ impl DirectoryError {
|
||||
);
|
||||
DirectoryError::Unsupported
|
||||
}
|
||||
|
||||
pub fn timeout(protocol: &str) -> Self {
|
||||
tracing::warn!(
|
||||
context = "directory",
|
||||
event = "error",
|
||||
protocol = protocol,
|
||||
"Directory timed out"
|
||||
);
|
||||
DirectoryError::TimedOut
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
46
crates/directory/src/smtp/config.rs
Normal file
46
crates/directory/src/smtp/config.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use mail_send::{smtp::tls::build_tls_connector, SmtpClientBuilder};
|
||||
use utils::config::{utils::AsKey, Config};
|
||||
|
||||
use crate::{config::build_pool, smtp::SmtpConnectionManager, Directory};
|
||||
|
||||
use super::SmtpDirectory;
|
||||
|
||||
impl SmtpDirectory {
|
||||
pub fn from_config(
|
||||
config: &Config,
|
||||
prefix: impl AsKey,
|
||||
is_lmtp: bool,
|
||||
) -> utils::config::Result<Arc<dyn Directory>> {
|
||||
let prefix = prefix.as_key();
|
||||
let address = config.value_require((&prefix, "address"))?;
|
||||
let tls_implicit: bool = config.property_or_static((&prefix, "tls.implicit"), "false")?;
|
||||
let port: u16 = config
|
||||
.property_or_static((&prefix, "port"), if tls_implicit { "465" } else { "25" })?;
|
||||
|
||||
let manager = SmtpConnectionManager {
|
||||
builder: SmtpClientBuilder {
|
||||
addr: format!("{address}:{port}"),
|
||||
timeout: config.property_or_static((&prefix, "timeout"), "30s")?,
|
||||
tls_connector: build_tls_connector(
|
||||
config.property_or_static((&prefix, "tls.allow-invalid-certs"), "false")?,
|
||||
),
|
||||
tls_hostname: address.to_string(),
|
||||
tls_implicit,
|
||||
is_lmtp,
|
||||
credentials: None,
|
||||
local_host: config
|
||||
.value("server.hostname")
|
||||
.unwrap_or("[127.0.0.1]")
|
||||
.to_string(),
|
||||
},
|
||||
max_rcpt: config.property_or_static((&prefix, "limits.rcpt"), "10")?,
|
||||
max_auth_errors: config.property_or_static((&prefix, "limits.auth-errors"), "3")?,
|
||||
};
|
||||
|
||||
Ok(Arc::new(SmtpDirectory {
|
||||
pool: build_pool(config, &prefix, manager)?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod config;
|
||||
pub mod lookup;
|
||||
pub mod pool;
|
||||
|
||||
|
||||
94
crates/directory/src/sql/config.rs
Normal file
94
crates/directory/src/sql/config.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use sqlx::any::AnyPoolOptions;
|
||||
use utils::config::{utils::AsKey, Config};
|
||||
|
||||
use crate::Directory;
|
||||
|
||||
use super::{SqlDirectory, SqlMappings};
|
||||
|
||||
impl SqlDirectory {
|
||||
pub fn from_config(
|
||||
config: &Config,
|
||||
prefix: impl AsKey,
|
||||
) -> utils::config::Result<Arc<dyn Directory>> {
|
||||
let prefix = prefix.as_key();
|
||||
let address = config.value_require((&prefix, "address"))?;
|
||||
|
||||
let pool = AnyPoolOptions::new()
|
||||
.max_connections(
|
||||
config
|
||||
.property((&prefix, "pool.max-connections"))?
|
||||
.unwrap_or(10),
|
||||
)
|
||||
.min_connections(
|
||||
config
|
||||
.property((&prefix, "pool.min-connections"))?
|
||||
.unwrap_or(0),
|
||||
)
|
||||
.idle_timeout(config.property((&prefix, "pool.idle-timeout"))?)
|
||||
.connect_lazy(address)
|
||||
.map_err(|err| format!("Failed to create connection pool for {address:?}: {err}"))?;
|
||||
|
||||
let mappings = SqlMappings {
|
||||
query_login: config
|
||||
.value((&prefix, "query.login"))
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
query_name: config
|
||||
.value((&prefix, "query.name"))
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
query_id: config
|
||||
.value((&prefix, "query.id"))
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
query_members: config
|
||||
.value((&prefix, "query.members"))
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
query_recipients: config
|
||||
.value((&prefix, "query.recipients"))
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
query_emails: config
|
||||
.value((&prefix, "query.emails"))
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
query_verify: config
|
||||
.value((&prefix, "query.verify"))
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
query_expand: config
|
||||
.value((&prefix, "query.expand"))
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
column_name: config
|
||||
.value((&prefix, "column.name"))
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
column_description: config
|
||||
.value((&prefix, "column.description"))
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
column_secret: config
|
||||
.value((&prefix, "column.secret"))
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
column_id: config
|
||||
.value((&prefix, "column.id"))
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
column_quota: config
|
||||
.value((&prefix, "column.quota"))
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
column_type: config
|
||||
.value((&prefix, "column.type"))
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
};
|
||||
|
||||
Ok(Arc::new(SqlDirectory { pool, mappings }))
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use sqlx::{Any, Pool};
|
||||
|
||||
pub mod config;
|
||||
pub mod lookup;
|
||||
|
||||
pub struct SqlDirectory {
|
||||
|
||||
Reference in New Issue
Block a user