use azure_data_cosmos_macros::CosmosOptions;
use super::env_parsing::{resolve_from_env, ValidationBounds};
const DEFAULT_MAX_SUMMARY_SIZE_BYTES: usize = 8 * 1024;
const MIN_MAX_SUMMARY_SIZE_BYTES: usize = 4 * 1024;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum DiagnosticsVerbosity {
#[default]
Default,
Summary,
Detailed,
}
impl DiagnosticsVerbosity {
pub fn as_str(&self) -> &'static str {
match self {
DiagnosticsVerbosity::Default => "default",
DiagnosticsVerbosity::Summary => "summary",
DiagnosticsVerbosity::Detailed => "detailed",
}
}
}
impl AsRef<str> for DiagnosticsVerbosity {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl std::fmt::Display for DiagnosticsVerbosity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl std::str::FromStr for DiagnosticsVerbosity {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"default" => Ok(DiagnosticsVerbosity::Default),
"summary" | "minimal" => Ok(DiagnosticsVerbosity::Summary),
"detailed" | "verbose" => Ok(DiagnosticsVerbosity::Detailed),
_ => Err(format!(
"Unknown diagnostics verbosity: '{}'. Expected 'default', 'summary', or 'detailed'",
s
)),
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DiagnosticsOptions {
pub(crate) max_summary_size_bytes: usize,
pub(crate) default_verbosity: DiagnosticsVerbosity,
}
impl Default for DiagnosticsOptions {
fn default() -> Self {
DiagnosticsOptionsBuilder::new()
.build()
.expect("Default DiagnosticsOptions should always be valid")
}
}
impl DiagnosticsOptions {
pub fn builder() -> DiagnosticsOptionsBuilder {
DiagnosticsOptionsBuilder::new()
}
pub fn max_summary_size_bytes(&self) -> usize {
self.max_summary_size_bytes
}
pub fn default_verbosity(&self) -> DiagnosticsVerbosity {
self.default_verbosity
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, CosmosOptions)]
#[options(env_only)]
pub struct DiagnosticsOptionsBuilder {
#[option(env = "AZURE_COSMOS_DIAGNOSTICS_MAX_SUMMARY_SIZE_BYTES")]
max_summary_size_bytes: Option<usize>,
#[option(env = "AZURE_COSMOS_DIAGNOSTICS_DEFAULT_VERBOSITY")]
default_verbosity: Option<DiagnosticsVerbosity>,
}
impl DiagnosticsOptionsBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_max_summary_size_bytes(mut self, size: usize) -> Self {
self.max_summary_size_bytes = Some(size);
self
}
pub fn with_default_verbosity(mut self, verbosity: DiagnosticsVerbosity) -> Self {
self.default_verbosity = Some(verbosity);
self
}
pub fn build(self) -> crate::error::Result<DiagnosticsOptions> {
let env = Self::from_env();
let max_summary_size_bytes = resolve_from_env(
self.max_summary_size_bytes,
env.max_summary_size_bytes,
"AZURE_COSMOS_DIAGNOSTICS_MAX_SUMMARY_SIZE_BYTES",
DEFAULT_MAX_SUMMARY_SIZE_BYTES,
ValidationBounds::min(MIN_MAX_SUMMARY_SIZE_BYTES),
)?;
let default_verbosity = self
.default_verbosity
.or(env.default_verbosity)
.unwrap_or(DiagnosticsVerbosity::Detailed);
Ok(DiagnosticsOptions {
max_summary_size_bytes,
default_verbosity,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults() {
let options = DiagnosticsOptions::default();
assert_eq!(options.max_summary_size_bytes, 8 * 1024);
assert_eq!(options.default_verbosity, DiagnosticsVerbosity::Detailed);
}
#[test]
fn env_config_from_env_vars_maps_names_to_fields() {
let cfg = DiagnosticsOptionsBuilder::from_env_vars(|key| match key {
"AZURE_COSMOS_DIAGNOSTICS_MAX_SUMMARY_SIZE_BYTES" => Ok("16384".to_string()),
"AZURE_COSMOS_DIAGNOSTICS_DEFAULT_VERBOSITY" => Ok("summary".to_string()),
_ => Err(std::env::VarError::NotPresent),
});
assert_eq!(cfg.max_summary_size_bytes, Some(16384));
assert_eq!(cfg.default_verbosity, Some(DiagnosticsVerbosity::Summary));
}
#[test]
fn env_config_from_env_vars_is_lenient_on_malformed_value() {
let cfg = DiagnosticsOptionsBuilder::from_env_vars(|key| match key {
"AZURE_COSMOS_DIAGNOSTICS_MAX_SUMMARY_SIZE_BYTES" => Ok("huge".to_string()),
"AZURE_COSMOS_DIAGNOSTICS_DEFAULT_VERBOSITY" => Ok("nonsense".to_string()),
_ => Err(std::env::VarError::NotPresent),
});
assert!(cfg.max_summary_size_bytes.is_none());
assert!(cfg.default_verbosity.is_none());
}
#[test]
fn custom_values() {
let options = DiagnosticsOptionsBuilder::new()
.with_max_summary_size_bytes(16 * 1024)
.with_default_verbosity(DiagnosticsVerbosity::Summary)
.build()
.unwrap();
assert_eq!(options.max_summary_size_bytes, 16 * 1024);
assert_eq!(options.default_verbosity, DiagnosticsVerbosity::Summary);
}
#[test]
fn max_summary_size_too_small() {
let result = DiagnosticsOptionsBuilder::new()
.with_max_summary_size_bytes(2 * 1024) .build();
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("must be at least 4096"));
}
#[test]
fn verbosity_from_str() {
assert_eq!(
"default".parse::<DiagnosticsVerbosity>().unwrap(),
DiagnosticsVerbosity::Default
);
assert_eq!(
"summary".parse::<DiagnosticsVerbosity>().unwrap(),
DiagnosticsVerbosity::Summary
);
assert_eq!(
"minimal".parse::<DiagnosticsVerbosity>().unwrap(),
DiagnosticsVerbosity::Summary
);
assert_eq!(
"detailed".parse::<DiagnosticsVerbosity>().unwrap(),
DiagnosticsVerbosity::Detailed
);
assert_eq!(
"verbose".parse::<DiagnosticsVerbosity>().unwrap(),
DiagnosticsVerbosity::Detailed
);
assert_eq!(
"DETAILED".parse::<DiagnosticsVerbosity>().unwrap(),
DiagnosticsVerbosity::Detailed
);
assert!("invalid".parse::<DiagnosticsVerbosity>().is_err());
}
#[test]
fn verbosity_display() {
assert_eq!(DiagnosticsVerbosity::Default.to_string(), "default");
assert_eq!(DiagnosticsVerbosity::Summary.to_string(), "summary");
assert_eq!(DiagnosticsVerbosity::Detailed.to_string(), "detailed");
}
}