use std::{ fmt::Display, fs, path::{Path, PathBuf}, str::FromStr, }; use anyhow::Context; use garde::Validate; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use sql_generation::generation::Opts; use strum::EnumString; use crate::profiles::{io::IOProfile, query::QueryProfile}; pub mod io; pub mod query; #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Validate)] #[serde(deny_unknown_fields, default)] pub struct Profile { #[garde(skip)] /// Experimental MVCC feature pub experimental_mvcc: bool, #[garde(dive)] pub io: IOProfile, #[garde(dive)] pub query: QueryProfile, } impl Default for Profile { fn default() -> Self { Self { experimental_mvcc: false, io: Default::default(), query: Default::default(), } } } impl Profile { pub fn write_heavy() -> Self { Profile { query: QueryProfile { gen_opts: Opts { // TODO: in the future tweak blob size for bigger inserts // TODO: increase number of rows as well ..Default::default() }, read_weight: 30, write_weight: 70, delete_weight: 0, update_weight: 0, ..Default::default() }, ..Default::default() } } pub fn parse_from_type(profile_type: ProfileType) -> anyhow::Result { let profile = match profile_type { ProfileType::Default => Profile::default(), ProfileType::WriteHeavy => Self::write_heavy(), ProfileType::Custom(path) => { Self::parse(path).with_context(|| "failed to parse JSON profile")? } }; Ok(profile) } // TODO: in the future handle extension and composability of profiles here pub fn parse(path: impl AsRef) -> anyhow::Result { let contents = fs::read_to_string(path)?; // use json5 so we can support comments and trailing commas let profile = json5::from_str(&contents)?; Ok(profile) } } #[derive( Debug, Default, Clone, Serialize, Deserialize, EnumString, PartialEq, Eq, PartialOrd, Ord, strum::Display, strum::VariantNames, )] #[serde(rename_all = "snake_case")] #[strum(ascii_case_insensitive, serialize_all = "snake_case")] pub enum ProfileType { #[default] Default, WriteHeavy, #[strum(disabled)] Custom(PathBuf), } impl ProfileType { pub fn parse(s: &str) -> anyhow::Result { if let Ok(prof) = ProfileType::from_str(s) { Ok(prof) } else if let path = PathBuf::from(s) && path.exists() { Ok(ProfileType::Custom(path)) } else { Err(anyhow::anyhow!( "failed identifying predifined profile or custom profile path" )) } } } /// Minimum value of field is dependent on another field in the struct fn min_dependent(min: &T) -> impl FnOnce(&T, &()) -> garde::Result + '_ { move |value, _| { if value < min { return Err(garde::Error::new(format!( "`{value}` is smaller than `{min}`" ))); } Ok(()) } } /// Maximum value of field is dependent on another field in the struct fn max_dependent(max: &T) -> impl FnOnce(&T, &()) -> garde::Result + '_ { move |value, _| { if value > max { return Err(garde::Error::new(format!( "`{value}` is bigger than `{max}`" ))); } Ok(()) } }