use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum GlobalTableMode {
None,
#[default]
Lookups,
All,
}
impl std::str::FromStr for GlobalTableMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"none" => Ok(GlobalTableMode::None),
"lookups" => Ok(GlobalTableMode::Lookups),
"all" => Ok(GlobalTableMode::All),
_ => Err(format!(
"Unknown global mode: {}. Valid options: none, lookups, all",
s
)),
}
}
}
impl std::fmt::Display for GlobalTableMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GlobalTableMode::None => write!(f, "none"),
GlobalTableMode::Lookups => write!(f, "lookups"),
GlobalTableMode::All => write!(f, "all"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TableClassification {
#[default]
Normal,
Root,
Lookup,
System,
Junction,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct TableConfig {
pub percent: Option<u32>,
pub rows: Option<usize>,
pub skip: bool,
pub classification: Option<TableClassification>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct DefaultConfig {
pub percent: Option<u32>,
pub rows: Option<usize>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct ClassificationConfig {
#[serde(default)]
pub global: Vec<String>,
#[serde(default)]
pub system: Vec<String>,
#[serde(default)]
pub lookup: Vec<String>,
#[serde(default)]
pub root: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct SampleYamlConfig {
pub default: DefaultConfig,
pub classification: ClassificationConfig,
#[serde(default)]
pub tables: HashMap<String, TableConfig>,
}
impl SampleYamlConfig {
pub fn load(path: &Path) -> anyhow::Result<Self> {
let content = fs::read_to_string(path)?;
let config: SampleYamlConfig = serde_yml::from_str(&content)?;
Ok(config)
}
pub fn get_table_config(&self, table_name: &str) -> Option<&TableConfig> {
self.tables.get(table_name).or_else(|| {
let lower = table_name.to_lowercase();
self.tables
.iter()
.find(|(k, _)| k.to_lowercase() == lower)
.map(|(_, v)| v)
})
}
pub fn get_classification(&self, table_name: &str) -> TableClassification {
if let Some(config) = self.get_table_config(table_name) {
if let Some(class) = config.classification {
return class;
}
}
let lower = table_name.to_lowercase();
if self
.classification
.global
.iter()
.any(|t| t.to_lowercase() == lower)
{
return TableClassification::Lookup;
}
if self
.classification
.system
.iter()
.any(|t| t.to_lowercase() == lower)
{
return TableClassification::System;
}
if self
.classification
.lookup
.iter()
.any(|t| t.to_lowercase() == lower)
{
return TableClassification::Lookup;
}
if self
.classification
.root
.iter()
.any(|t| t.to_lowercase() == lower)
{
return TableClassification::Root;
}
TableClassification::Normal
}
pub fn should_skip(&self, table_name: &str) -> bool {
if let Some(config) = self.get_table_config(table_name) {
return config.skip;
}
false
}
pub fn get_percent(&self, table_name: &str) -> Option<u32> {
if let Some(config) = self.get_table_config(table_name) {
if config.percent.is_some() {
return config.percent;
}
}
self.default.percent
}
pub fn get_rows(&self, table_name: &str) -> Option<usize> {
if let Some(config) = self.get_table_config(table_name) {
if config.rows.is_some() {
return config.rows;
}
}
self.default.rows
}
}
pub struct DefaultClassifier;
impl DefaultClassifier {
const SYSTEM_PATTERNS: &'static [&'static str] = &[
"migrations",
"failed_jobs",
"job_batches",
"jobs",
"cache",
"cache_locks",
"sessions",
"password_reset_tokens",
"personal_access_tokens",
"telescope_entries",
"telescope_entries_tags",
"telescope_monitoring",
"pulse_",
"horizon_",
];
const LOOKUP_PATTERNS: &'static [&'static str] = &[
"countries",
"states",
"provinces",
"cities",
"currencies",
"languages",
"timezones",
"permissions",
"roles",
"settings",
];
pub fn classify(table_name: &str) -> TableClassification {
let lower = table_name.to_lowercase();
for pattern in Self::SYSTEM_PATTERNS {
if lower.starts_with(pattern) || lower == *pattern {
return TableClassification::System;
}
}
for pattern in Self::LOOKUP_PATTERNS {
if lower == *pattern {
return TableClassification::Lookup;
}
}
TableClassification::Normal
}
}