use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("YAML parsing error: {0}")]
Yaml(String),
#[error("JSON parsing error: {0}")]
Json(#[from] serde_json::Error),
#[error("Validation error: {0}")]
Validation(String),
#[error("Generation error: {0}")]
Generation(String),
#[error("Unsupported format: {0}")]
UnsupportedFormat(String),
}
pub type ConfigResult<T> = Result<T, ConfigError>;
#[cfg(feature = "serde")]
mod implementation {
use super::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerationConfig {
pub metadata: ConfigMetadata,
pub datasets: Vec<DatasetSpec>,
pub global_settings: Option<GlobalSettings>,
pub export: Option<ExportConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigMetadata {
pub name: String,
pub version: String,
pub description: Option<String>,
pub author: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<String>,
#[serde(default)]
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum DatasetSpec {
#[serde(rename = "classification")]
Classification(ClassificationConfig),
#[serde(rename = "regression")]
Regression(RegressionConfig),
#[serde(rename = "clustering")]
Clustering(ClusteringConfig),
#[serde(rename = "manifold")]
Manifold(ManifoldConfig),
#[serde(rename = "time_series")]
TimeSeries(TimeSeriesConfig),
#[serde(rename = "custom")]
Custom(CustomDatasetConfig),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClassificationConfig {
pub name: String,
pub n_samples: usize,
pub n_features: usize,
pub n_classes: usize,
pub n_informative: Option<usize>,
pub random_state: Option<u64>,
pub use_simd: Option<bool>,
pub feature_names: Option<Vec<String>>,
pub class_names: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegressionConfig {
pub name: String,
pub n_samples: usize,
pub n_features: usize,
pub n_informative: Option<usize>,
pub noise: Option<f64>,
pub random_state: Option<u64>,
pub use_simd: Option<bool>,
pub feature_names: Option<Vec<String>>,
pub target_names: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClusteringConfig {
pub name: String,
pub n_samples: usize,
pub n_features: usize,
pub centers: Option<usize>,
pub cluster_std: Option<f64>,
pub random_state: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ManifoldConfig {
pub name: String,
pub manifold_type: ManifoldType,
pub n_samples: usize,
pub noise: Option<f64>,
pub random_state: Option<u64>,
#[serde(flatten)]
pub parameters: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ManifoldType {
SwissRoll,
SCurve,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeSeriesConfig {
pub name: String,
pub n_timesteps: usize,
pub n_features: usize,
pub noise: Option<f64>,
pub random_state: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CustomDatasetConfig {
pub name: String,
pub generator: String,
pub parameters: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GlobalSettings {
pub default_random_state: Option<u64>,
pub default_simd: Option<bool>,
pub n_workers: Option<usize>,
pub validation: Option<ValidationSettings>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationSettings {
pub enabled: bool,
pub tests: Vec<StatisticalTest>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StatisticalTest {
Normality,
Quality,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportConfig {
pub output_dir: String,
pub formats: Vec<ExportFormat>,
pub include_metadata: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExportFormat {
Csv,
Json,
Parquet,
}
pub struct ConfigLoader;
impl ConfigLoader {
pub fn load_from_file<P: AsRef<Path>>(path: P) -> ConfigResult<GenerationConfig> {
let path = path.as_ref();
let contents = fs::read_to_string(path)?;
match path.extension().and_then(|s| s.to_str()) {
Some("yaml") | Some("yml") => Self::load_from_yaml(&contents),
Some("json") => Self::load_from_json(&contents),
Some(ext) => Err(ConfigError::UnsupportedFormat(format!(
"Unsupported config format: {}. Supported formats: yaml, yml, json",
ext
))),
None => Err(ConfigError::UnsupportedFormat(
"No file extension found. Supported formats: yaml, yml, json".to_string(),
)),
}
}
pub fn load_from_yaml(yaml_str: &str) -> ConfigResult<GenerationConfig> {
serde_yaml::from_str(yaml_str).map_err(|e| ConfigError::Yaml(e.to_string()))
}
pub fn load_from_json(json_str: &str) -> ConfigResult<GenerationConfig> {
Ok(serde_json::from_str(json_str)?)
}
pub fn save_to_file<P: AsRef<Path>>(
config: &GenerationConfig,
path: P,
) -> ConfigResult<()> {
let path = path.as_ref();
let contents = match path.extension().and_then(|s| s.to_str()) {
Some("yaml") | Some("yml") => Self::to_yaml(config)?,
Some("json") => Self::to_json(config)?,
Some(ext) => {
return Err(ConfigError::UnsupportedFormat(format!(
"Unsupported config format: {}",
ext
)))
}
None => {
return Err(ConfigError::UnsupportedFormat(
"No file extension found".to_string(),
))
}
};
fs::write(path, contents)?;
Ok(())
}
pub fn to_yaml(config: &GenerationConfig) -> ConfigResult<String> {
serde_yaml::to_string(config).map_err(|e| ConfigError::Yaml(e.to_string()))
}
pub fn to_json(config: &GenerationConfig) -> ConfigResult<String> {
Ok(serde_json::to_string_pretty(config)?)
}
}
pub fn generate_example_config() -> GenerationConfig {
GenerationConfig {
metadata: ConfigMetadata {
name: "Example Dataset Configuration".to_string(),
version: "1.0.0".to_string(),
description: Some("An example configuration".to_string()),
author: Some("sklears-datasets".to_string()),
created_at: Some("2024-01-01T00:00:00Z".to_string()),
tags: vec!["example".to_string()],
},
datasets: vec![
DatasetSpec::Classification(ClassificationConfig {
name: "iris_like".to_string(),
n_samples: 150,
n_features: 4,
n_classes: 3,
n_informative: Some(4),
random_state: Some(42),
use_simd: Some(true),
feature_names: Some(vec![
"sepal_length".to_string(),
"sepal_width".to_string(),
"petal_length".to_string(),
"petal_width".to_string(),
]),
class_names: Some(vec![
"setosa".to_string(),
"versicolor".to_string(),
"virginica".to_string(),
]),
}),
DatasetSpec::Regression(RegressionConfig {
name: "boston_like".to_string(),
n_samples: 500,
n_features: 13,
n_informative: Some(10),
noise: Some(10.0),
random_state: Some(42),
use_simd: Some(true),
feature_names: None,
target_names: Some(vec!["price".to_string()]),
}),
],
global_settings: Some(GlobalSettings {
default_random_state: Some(42),
default_simd: Some(true),
n_workers: Some(4),
validation: Some(ValidationSettings {
enabled: true,
tests: vec![StatisticalTest::Quality],
}),
}),
export: Some(ExportConfig {
output_dir: "./datasets".to_string(),
formats: vec![ExportFormat::Csv, ExportFormat::Json],
include_metadata: Some(true),
}),
}
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_example_config_generation() {
let config = generate_example_config();
assert_eq!(config.datasets.len(), 2);
}
#[test]
fn test_config_serialization() {
let config = generate_example_config();
let yaml_str = ConfigLoader::to_yaml(&config).expect("operation should succeed");
assert!(!yaml_str.is_empty());
assert!(yaml_str.contains("iris_like"));
let json_str = ConfigLoader::to_json(&config).expect("operation should succeed");
assert!(!json_str.is_empty());
assert!(json_str.contains("iris_like"));
}
#[test]
fn test_config_deserialization() {
let config = generate_example_config();
let yaml_str = ConfigLoader::to_yaml(&config).expect("operation should succeed");
let config_from_yaml = ConfigLoader::load_from_yaml(&yaml_str).expect("operation should succeed");
assert_eq!(config.datasets.len(), config_from_yaml.datasets.len());
let json_str = ConfigLoader::to_json(&config).expect("operation should succeed");
let config_from_json = ConfigLoader::load_from_json(&json_str).expect("operation should succeed");
assert_eq!(config.datasets.len(), config_from_json.datasets.len());
}
#[test]
fn test_file_operations() {
let config = generate_example_config();
let dir = tempdir().expect("operation should succeed");
let yaml_path = dir.path().join("config.yaml");
ConfigLoader::save_to_file(&config, &yaml_path).expect("operation should succeed");
let loaded_config = ConfigLoader::load_from_file(&yaml_path).expect("operation should succeed");
assert_eq!(config.datasets.len(), loaded_config.datasets.len());
let json_path = dir.path().join("config.json");
ConfigLoader::save_to_file(&config, &json_path).expect("operation should succeed");
let loaded_config = ConfigLoader::load_from_file(&json_path).expect("operation should succeed");
assert_eq!(config.datasets.len(), loaded_config.datasets.len());
}
}
}
#[cfg(feature = "serde")]
pub use implementation::*;
#[cfg(not(feature = "serde"))]
mod stubs {
use super::*;
pub struct ConfigLoader;
pub struct GenerationConfig;
pub struct ConfigMetadata;
pub struct DatasetSpec;
pub struct ClassificationConfig;
pub struct RegressionConfig;
pub struct ClusteringConfig;
pub struct ManifoldConfig;
pub struct TimeSeriesConfig;
pub struct CustomDatasetConfig;
pub struct GlobalSettings;
pub struct ValidationSettings;
pub struct StatisticalTest;
pub struct ExportConfig;
pub struct ExportFormat;
pub struct ManifoldType;
impl ConfigLoader {
pub fn load_from_file<P: AsRef<std::path::Path>>(
_path: P,
) -> ConfigResult<GenerationConfig> {
Err(ConfigError::UnsupportedFormat(
"Configuration management requires the 'serde' feature to be enabled".to_string(),
))
}
pub fn load_from_yaml(_yaml_str: &str) -> ConfigResult<GenerationConfig> {
Err(ConfigError::UnsupportedFormat(
"YAML support requires the 'serde' feature to be enabled".to_string(),
))
}
pub fn load_from_json(_json_str: &str) -> ConfigResult<GenerationConfig> {
Err(ConfigError::UnsupportedFormat(
"JSON support requires the 'serde' feature to be enabled".to_string(),
))
}
}
pub fn generate_example_config() -> ConfigResult<GenerationConfig> {
Err(ConfigError::UnsupportedFormat(
"Configuration generation requires the 'serde' feature to be enabled".to_string(),
))
}
}
#[cfg(not(feature = "serde"))]
pub use stubs::*;