use std::{collections::HashMap, time::Duration};
use crate::bson::Array;
use mongocrypt::ctx::KmsProvider;
use serde::Deserialize;
use crate::{
bson::{Bson, Document},
client::options::TlsOptions,
error::{Error, Result},
serde_util,
Namespace,
};
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub(crate) struct AutoEncryptionOptions {
#[serde(skip)]
pub(crate) key_vault_client: Option<crate::Client>,
#[serde(default = "default_key_vault_namespace")]
pub(crate) key_vault_namespace: Namespace,
pub(crate) kms_providers: KmsProviders,
pub(crate) schema_map: Option<HashMap<String, Document>>,
pub(crate) bypass_auto_encryption: Option<bool>,
pub(crate) extra_options: Option<Document>,
pub(crate) encrypted_fields_map: Option<HashMap<String, Document>>,
pub(crate) bypass_query_analysis: Option<bool>,
#[cfg(test)]
#[serde(skip)]
pub(crate) disable_crypt_shared: Option<bool>,
#[serde(
default,
rename = "keyExpirationMS",
deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis"
)]
pub(crate) key_cache_expiration: Option<Duration>,
}
fn default_key_vault_namespace() -> Namespace {
Namespace {
db: "keyvault".to_string(),
coll: "datakeys".to_string(),
}
}
impl AutoEncryptionOptions {
pub(crate) fn new(key_vault_namespace: Namespace, kms_providers: KmsProviders) -> Self {
Self {
key_vault_namespace,
kms_providers,
key_vault_client: None,
schema_map: None,
bypass_auto_encryption: None,
extra_options: None,
encrypted_fields_map: None,
bypass_query_analysis: None,
#[cfg(test)]
disable_crypt_shared: None,
key_cache_expiration: None,
}
}
}
#[derive(Deserialize, Debug, Clone)]
pub(crate) struct KmsProviders {
#[serde(flatten)]
credentials: HashMap<KmsProvider, Document>,
#[serde(skip)]
tls_options: Option<KmsProvidersTlsOptions>,
}
pub(crate) type KmsProvidersTlsOptions = HashMap<KmsProvider, TlsOptions>;
impl KmsProviders {
pub(crate) fn new(
providers: impl IntoIterator<Item = (KmsProvider, crate::bson::Document, Option<TlsOptions>)>,
) -> Result<Self> {
let mut credentials = HashMap::new();
let mut tls_options = None;
for (provider, conf, tls) in providers.into_iter() {
credentials.insert(provider.clone(), conf);
if let Some(tls) = tls {
tls_options
.get_or_insert_with(KmsProvidersTlsOptions::new)
.insert(provider, tls);
}
}
if credentials.is_empty() {
return Err(crate::error::Error::invalid_argument("empty kms_providers"));
}
Ok(Self {
credentials,
tls_options,
})
}
pub(crate) fn credentials_doc(&self) -> Result<Document> {
Ok(crate::bson_compat::serialize_to_document(
&self.credentials,
)?)
}
pub(crate) fn tls_options(&self) -> Option<&KmsProvidersTlsOptions> {
self.tls_options.as_ref()
}
pub(crate) fn credentials(&self) -> &HashMap<KmsProvider, Document> {
&self.credentials
}
}
impl AutoEncryptionOptions {
pub(crate) fn extra_option<'a, Opt: ExtraOption<'a>>(
&'a self,
opt: &Opt,
) -> Result<Option<Opt::Output>> {
let key = opt.key();
match self.extra_options.as_ref().and_then(|o| o.get(key)) {
None => Ok(None),
Some(b) => match Opt::as_type(b) {
Some(v) => Ok(Some(v)),
None => Err(Error::invalid_argument(format!(
"unexpected type for extra option {key:?}: {b:?}"
))),
},
}
}
}
pub(crate) trait ExtraOption<'a> {
type Output;
fn key(&self) -> &'static str;
fn as_type(input: &'a Bson) -> Option<Self::Output>;
}
pub(crate) struct ExtraOptionStr(&'static str);
impl<'a> ExtraOption<'a> for ExtraOptionStr {
type Output = &'a str;
fn key(&self) -> &'static str {
self.0
}
fn as_type(input: &'a Bson) -> Option<&'a str> {
input.as_str()
}
}
pub(crate) struct ExtraOptionBool(&'static str);
impl<'a> ExtraOption<'a> for ExtraOptionBool {
type Output = bool;
fn key(&self) -> &'static str {
self.0
}
fn as_type(input: &'a Bson) -> Option<bool> {
input.as_bool()
}
}
pub(crate) struct ExtraOptionArray(&'static str);
impl<'a> ExtraOption<'a> for ExtraOptionArray {
type Output = &'a Array;
fn key(&self) -> &'static str {
self.0
}
fn as_type(input: &'a Bson) -> Option<&'a Array> {
input.as_array()
}
}
pub(crate) const EO_MONGOCRYPTD_URI: ExtraOptionStr = ExtraOptionStr("mongocryptdURI");
pub(crate) const EO_MONGOCRYPTD_BYPASS_SPAWN: ExtraOptionBool =
ExtraOptionBool("mongocryptdBypassSpawn");
pub(crate) const EO_MONGOCRYPTD_SPAWN_PATH: ExtraOptionStr = ExtraOptionStr("mongocryptdSpawnPath");
pub(crate) const EO_MONGOCRYPTD_SPAWN_ARGS: ExtraOptionArray =
ExtraOptionArray("mongocryptdSpawnArgs");
pub(crate) const EO_CRYPT_SHARED_LIB_PATH: ExtraOptionStr = ExtraOptionStr("cryptSharedLibPath");
pub(crate) const EO_CRYPT_SHARED_REQUIRED: ExtraOptionBool = ExtraOptionBool("cryptSharedRequired");