use std::fmt;
use std::str::FromStr;
use crate::config::LaminarConfig;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Profile {
#[default]
BareMetal,
Embedded,
Durable,
Delta,
}
impl Profile {
#[must_use]
pub fn from_config(config: &LaminarConfig, has_discovery: bool) -> Self {
if has_discovery {
return Self::Delta;
}
if let Some(url) = &config.object_store_url {
if url.starts_with("s3://")
|| url.starts_with("gs://")
|| url.starts_with("az://")
|| url.starts_with("abfs://")
{
return Self::Durable;
}
if url.starts_with("file://") {
return Self::Embedded;
}
}
if config.storage_dir.is_some() {
return Self::Embedded;
}
Self::BareMetal
}
pub fn validate_features(self) -> Result<(), ProfileError> {
match self {
Self::BareMetal | Self::Embedded | Self::Durable | Self::Delta => Ok(()),
}
}
pub fn validate_config(
self,
config: &LaminarConfig,
object_store_url: Option<&str>,
) -> Result<(), ProfileError> {
match self {
Self::BareMetal => Ok(()),
Self::Embedded => {
if config.storage_dir.is_none() {
return Err(ProfileError::RequirementNotMet(
"Embedded profile requires a storage_dir".into(),
));
}
Ok(())
}
Self::Durable | Self::Delta => {
if object_store_url.is_none() {
return Err(ProfileError::RequirementNotMet(
"Durable/Delta profile requires an \
object_store_url"
.into(),
));
}
Ok(())
}
}
}
pub fn apply_defaults(self, config: &mut LaminarConfig) {
match self {
Self::BareMetal => {
}
Self::Embedded => {
if config.default_buffer_size == LaminarConfig::default().default_buffer_size {
config.default_buffer_size = 32_768;
}
}
Self::Durable => {
if config.default_buffer_size == LaminarConfig::default().default_buffer_size {
config.default_buffer_size = 131_072;
}
}
Self::Delta => {
if config.default_buffer_size == LaminarConfig::default().default_buffer_size {
config.default_buffer_size = 262_144;
}
}
}
}
}
impl FromStr for Profile {
type Err = ProfileError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"bare_metal" | "baremetal" | "bare-metal" => Ok(Self::BareMetal),
"embedded" => Ok(Self::Embedded),
"durable" => Ok(Self::Durable),
"delta" => Ok(Self::Delta),
_ => Err(ProfileError::UnknownProfileName(s.into())),
}
}
}
impl fmt::Display for Profile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BareMetal => write!(f, "bare_metal"),
Self::Embedded => write!(f, "embedded"),
Self::Durable => write!(f, "durable"),
Self::Delta => write!(f, "delta"),
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum ProfileError {
#[error("profile requirement not met: {0}")]
RequirementNotMet(String),
#[error("feature `{0}` not compiled — enable it in Cargo.toml")]
FeatureNotCompiled(String),
#[error("unknown profile name: {0}")]
UnknownProfileName(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bare_metal_zero_config() {
let config = LaminarConfig::default();
let profile = Profile::BareMetal;
assert!(profile.validate_features().is_ok());
assert!(profile.validate_config(&config, None).is_ok());
}
#[test]
fn test_embedded_requires_storage_dir() {
let config = LaminarConfig::default();
let result = Profile::Embedded.validate_config(&config, None);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
ProfileError::RequirementNotMet(_)
));
}
#[test]
fn test_durable_fails_without_object_store_url() {
let config = LaminarConfig::default();
let result = Profile::Durable.validate_config(&config, None);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
ProfileError::RequirementNotMet(_)
));
}
#[test]
fn test_profile_from_str() {
assert_eq!(Profile::from_str("bare_metal").unwrap(), Profile::BareMetal);
assert_eq!(Profile::from_str("baremetal").unwrap(), Profile::BareMetal);
assert_eq!(Profile::from_str("bare-metal").unwrap(), Profile::BareMetal);
assert_eq!(Profile::from_str("embedded").unwrap(), Profile::Embedded);
assert_eq!(Profile::from_str("durable").unwrap(), Profile::Durable);
assert_eq!(Profile::from_str("delta").unwrap(), Profile::Delta);
assert_eq!(Profile::from_str("DURABLE").unwrap(), Profile::Durable);
assert!(Profile::from_str("quantum").is_err());
assert!(matches!(
Profile::from_str("quantum").unwrap_err(),
ProfileError::UnknownProfileName(_)
));
}
#[test]
fn test_all_profiles_validate_features() {
assert!(Profile::BareMetal.validate_features().is_ok());
assert!(Profile::Embedded.validate_features().is_ok());
assert!(Profile::Durable.validate_features().is_ok());
assert!(Profile::Delta.validate_features().is_ok());
}
#[test]
fn test_profile_display() {
assert_eq!(Profile::BareMetal.to_string(), "bare_metal");
assert_eq!(Profile::Embedded.to_string(), "embedded");
assert_eq!(Profile::Durable.to_string(), "durable");
assert_eq!(Profile::Delta.to_string(), "delta");
}
#[test]
fn test_profile_default() {
assert_eq!(Profile::default(), Profile::BareMetal);
}
#[test]
fn test_apply_defaults_bare_metal_noop() {
let mut config = LaminarConfig::default();
let original_buffer = config.default_buffer_size;
Profile::BareMetal.apply_defaults(&mut config);
assert_eq!(config.default_buffer_size, original_buffer);
}
#[test]
fn test_apply_defaults_does_not_override_user_values() {
let mut config = LaminarConfig {
default_buffer_size: 999,
..LaminarConfig::default()
};
Profile::Durable.apply_defaults(&mut config);
assert_eq!(config.default_buffer_size, 999);
}
#[test]
fn test_from_config_bare_metal() {
let config = LaminarConfig::default();
assert_eq!(Profile::from_config(&config, false), Profile::BareMetal);
}
#[test]
fn test_from_config_embedded_storage_dir() {
let config = LaminarConfig {
storage_dir: Some(std::path::PathBuf::from("/tmp/data")),
..LaminarConfig::default()
};
assert_eq!(Profile::from_config(&config, false), Profile::Embedded);
}
#[test]
fn test_from_config_embedded_file_url() {
let config = LaminarConfig {
object_store_url: Some("file:///tmp/checkpoints".to_string()),
..LaminarConfig::default()
};
assert_eq!(Profile::from_config(&config, false), Profile::Embedded);
}
#[test]
fn test_from_config_durable_s3() {
let config = LaminarConfig {
object_store_url: Some("s3://my-bucket/prefix".to_string()),
..LaminarConfig::default()
};
assert_eq!(Profile::from_config(&config, false), Profile::Durable);
}
#[test]
fn test_from_config_durable_gs() {
let config = LaminarConfig {
object_store_url: Some("gs://my-bucket/prefix".to_string()),
..LaminarConfig::default()
};
assert_eq!(Profile::from_config(&config, false), Profile::Durable);
}
#[test]
fn test_from_config_durable_az() {
let config = LaminarConfig {
object_store_url: Some("az://container/prefix".to_string()),
..LaminarConfig::default()
};
assert_eq!(Profile::from_config(&config, false), Profile::Durable);
}
#[test]
fn test_from_config_durable_abfs() {
let config = LaminarConfig {
object_store_url: Some("abfs://container/prefix".to_string()),
..LaminarConfig::default()
};
assert_eq!(Profile::from_config(&config, false), Profile::Durable);
}
#[test]
fn test_from_config_delta() {
let config = LaminarConfig::default();
assert_eq!(Profile::from_config(&config, true), Profile::Delta);
}
#[test]
fn test_from_config_delta_overrides_url() {
let config = LaminarConfig {
object_store_url: Some("s3://bucket/prefix".to_string()),
..LaminarConfig::default()
};
assert_eq!(Profile::from_config(&config, true), Profile::Delta);
}
}