use std::fs;
use tempfile::NamedTempFile;
use crate::{
config::{Config, SplTokenConfig},
error::KoraError,
};
#[derive(Default)]
pub struct ConfigBuilder {
validation: ValidationSection,
kora: KoraSection,
custom_sections: Vec<String>,
}
struct ValidationSection {
max_allowed_lamports: u64,
max_signatures: u64,
allowed_programs: Vec<String>,
allowed_tokens: Vec<String>,
allowed_spl_paid_tokens: SplTokenConfig,
disallowed_accounts: Vec<String>,
price_source: String,
price_config: Option<String>,
token_2022_config: Option<String>,
}
struct KoraSection {
rate_limit: u64,
max_request_body_size: Option<usize>,
enabled_methods: Option<String>,
cache_config: Option<String>,
usage_limit_config: Option<String>,
}
impl Default for ValidationSection {
fn default() -> Self {
Self {
max_allowed_lamports: 1000000000,
max_signatures: 10,
allowed_programs: vec!["program1".to_string()],
allowed_tokens: vec!["token1".to_string()],
allowed_spl_paid_tokens: SplTokenConfig::Allowlist(vec!["token2".to_string()]),
disallowed_accounts: vec![],
price_source: "Jupiter".to_string(),
price_config: None,
token_2022_config: None,
}
}
}
impl Default for KoraSection {
fn default() -> Self {
Self {
rate_limit: 100,
max_request_body_size: None,
enabled_methods: None,
cache_config: None,
usage_limit_config: None,
}
}
}
impl ConfigBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_programs(mut self, programs: Vec<&str>) -> Self {
self.validation.allowed_programs = programs.iter().map(|s| s.to_string()).collect();
self
}
pub fn with_tokens(mut self, tokens: Vec<&str>) -> Self {
self.validation.allowed_tokens = tokens.iter().map(|s| s.to_string()).collect();
self
}
pub fn with_spl_paid_tokens(mut self, spl_payment_config: SplTokenConfig) -> Self {
self.validation.allowed_spl_paid_tokens = spl_payment_config;
self
}
pub fn with_disallowed_accounts(mut self, accounts: Vec<&str>) -> Self {
self.validation.disallowed_accounts = accounts.iter().map(|s| s.to_string()).collect();
self
}
pub fn with_margin_price(mut self, margin: f64) -> Self {
self.validation.price_config =
Some(format!("[validation.price]\ntype = \"margin\"\nmargin = {margin}\n"));
self
}
pub fn with_fixed_price(mut self, amount: u64, token: &str) -> Self {
self.validation.price_config = Some(format!(
"[validation.price]\ntype = \"fixed\"\namount = {amount}\ntoken = \"{token}\"\nstrict = false\n"
));
self
}
pub fn with_free_price(mut self) -> Self {
self.validation.price_config = Some("[validation.price]\ntype = \"free\"\n".to_string());
self
}
pub fn with_invalid_price(mut self, price_type: &str) -> Self {
self.validation.price_config =
Some(format!("[validation.price]\ntype = \"{price_type}\"\nmargin = 0.1\n"));
self
}
pub fn with_token2022_extensions(
mut self,
mint_exts: Vec<&str>,
account_exts: Vec<&str>,
) -> Self {
let mint_list =
mint_exts.iter().map(|ext| format!("\"{ext}\"")).collect::<Vec<_>>().join(", ");
let account_list =
account_exts.iter().map(|ext| format!("\"{ext}\"")).collect::<Vec<_>>().join(", ");
self.validation.token_2022_config = Some(format!(
"[validation.token_2022]\nblocked_mint_extensions = [{mint_list}]\nblocked_account_extensions = [{account_list}]\n"
));
self
}
pub fn with_enabled_methods(mut self, methods: &[(&str, bool)]) -> Self {
let method_config = methods
.iter()
.map(|(name, enabled)| format!("{name} = {enabled}"))
.collect::<Vec<_>>()
.join("\n");
self.kora.enabled_methods = Some(format!("[kora.enabled_methods]\n{method_config}\n"));
self
}
pub fn with_cache_config(
mut self,
url: Option<&str>,
enabled: bool,
default_ttl: u64,
account_ttl: u64,
) -> Self {
let url_line = match url {
Some(u) => format!("url = \"{u}\"\n"),
None => String::new(),
};
self.kora.cache_config = Some(format!(
"[kora.cache]\n{url_line}enabled = {enabled}\ndefault_ttl = {default_ttl}\naccount_ttl = {account_ttl}\n"
));
self
}
pub fn with_usage_limit_config(
mut self,
enabled: bool,
cache_url: Option<&str>,
max_transactions: u64,
fallback_if_unavailable: bool,
) -> Self {
let cache_url_line = match cache_url {
Some(url) => format!("cache_url = \"{url}\"\n"),
None => String::new(),
};
self.kora.usage_limit_config = Some(format!(
"[kora.usage_limit]\nenabled = {enabled}\n{cache_url_line}max_transactions = {max_transactions}\nfallback_if_unavailable = {fallback_if_unavailable}\n"
));
self
}
pub fn with_max_request_body_size(mut self, size: usize) -> Self {
self.kora.max_request_body_size = Some(size);
self
}
pub fn build_toml(&self) -> String {
let programs_list = self
.validation
.allowed_programs
.iter()
.map(|p| format!("\"{p}\""))
.collect::<Vec<_>>()
.join(", ");
let tokens_list = self
.validation
.allowed_tokens
.iter()
.map(|t| format!("\"{t}\""))
.collect::<Vec<_>>()
.join(", ");
let spl_tokens_config = match self.validation.allowed_spl_paid_tokens {
SplTokenConfig::Allowlist(ref tokens) => format!(
"[{}]",
tokens.iter().map(|t| format!("\"{t}\"")).collect::<Vec<_>>().join(", ")
),
SplTokenConfig::All => format!("\"{}\"", "All"),
};
let disallowed_list = if self.validation.disallowed_accounts.is_empty() {
"[]".to_string()
} else {
format!(
"[{}]",
self.validation
.disallowed_accounts
.iter()
.map(|a| format!("\"{a}\""))
.collect::<Vec<_>>()
.join(", ")
)
};
let mut toml = format!(
"[validation]\n\
max_allowed_lamports = {}\n\
max_signatures = {}\n\
allowed_programs = [{}]\n\
allowed_tokens = [{}]\n\
allowed_spl_paid_tokens = {}\n\
disallowed_accounts = {}\n\
price_source = \"{}\"\n\n",
self.validation.max_allowed_lamports,
self.validation.max_signatures,
programs_list,
tokens_list,
spl_tokens_config,
disallowed_list,
self.validation.price_source
);
if let Some(ref price_config) = self.validation.price_config {
toml.push_str(&format!("{price_config}\n"));
}
if let Some(ref token_config) = self.validation.token_2022_config {
toml.push_str(&format!("{token_config}\n"));
}
toml.push_str(&format!("[kora]\nrate_limit = {}\n", self.kora.rate_limit));
if let Some(size) = self.kora.max_request_body_size {
toml.push_str(&format!("max_request_body_size = {size}\n"));
}
if let Some(ref methods_config) = self.kora.enabled_methods {
toml.push_str(&format!("{methods_config}\n"));
}
if let Some(ref cache_config) = self.kora.cache_config {
toml.push_str(&format!("{cache_config}\n"));
}
if let Some(ref usage_limit_config) = self.kora.usage_limit_config {
toml.push_str(&format!("{usage_limit_config}\n"));
}
for custom in &self.custom_sections {
toml.push_str(&format!("{custom}\n"));
}
toml
}
pub fn build_config(&self) -> Result<Config, KoraError> {
let toml_content = self.build_toml();
let temp_file = NamedTempFile::new().unwrap();
fs::write(&temp_file, toml_content).unwrap();
Config::load_config(temp_file.path())
}
}
pub fn create_invalid_config(content: &str) -> Result<Config, KoraError> {
let temp_file = NamedTempFile::new().unwrap();
fs::write(&temp_file, content).unwrap();
Config::load_config(temp_file.path())
}