use anyhow::Context;
use perfgate_client::{BaselineClient, ClientConfig, FallbackClient, FallbackStorage};
use perfgate_types::{BaselineServerConfig, ConfigFile};
use std::fs;
use std::path::Path;
#[derive(Debug, Clone, Default)]
pub struct ResolvedServerConfig {
pub url: Option<String>,
pub api_key: Option<String>,
pub project: Option<String>,
pub fallback_to_local: bool,
}
impl ResolvedServerConfig {
pub fn is_configured(&self) -> bool {
self.url.as_ref().is_some_and(|u| !u.is_empty())
}
pub fn create_client(&self) -> anyhow::Result<Option<BaselineClient>> {
if !self.is_configured() {
return Ok(None);
}
let url = self.url.as_ref().unwrap();
let mut config = ClientConfig::new(url);
if let Some(api_key) = &self.api_key {
config = config.with_api_key(api_key);
}
let client = BaselineClient::new(config)
.with_context(|| format!("Failed to create baseline client for {}", url))?;
Ok(Some(client))
}
pub fn create_fallback_client(
&self,
fallback_dir: Option<&Path>,
) -> anyhow::Result<Option<FallbackClient>> {
let client = match self.create_client()? {
Some(c) => c,
None => return Ok(None),
};
let fallback = if self.fallback_to_local {
fallback_dir.map(|dir| FallbackStorage::local(dir.to_path_buf()))
} else {
None
};
Ok(Some(FallbackClient::new(client, fallback)))
}
pub fn require_fallback_client(
&self,
fallback_dir: Option<&Path>,
error_msg: &str,
) -> anyhow::Result<FallbackClient> {
self.create_fallback_client(fallback_dir)?
.ok_or_else(|| anyhow::anyhow!(error_msg.to_string()))
}
pub fn resolve_project(&self, project: Option<String>) -> anyhow::Result<String> {
project.or_else(|| self.project.clone()).ok_or_else(|| {
anyhow::anyhow!(
"--project is required (or set --project flag, PERFGATE_PROJECT, or [baseline_server].project in perfgate.toml)"
)
})
}
}
pub fn load_config_file(path: &Path) -> anyhow::Result<ConfigFile> {
if !path.exists() {
return Ok(ConfigFile::default());
}
let content = fs::read_to_string(path).with_context(|| format!("read {}", path.display()))?;
if path
.extension()
.and_then(|ext| ext.to_str())
.is_some_and(|ext| ext == "json")
{
serde_json::from_str::<ConfigFile>(&content)
.with_context(|| format!("parse {}", path.display()))
} else {
toml::from_str::<ConfigFile>(&content).with_context(|| format!("parse {}", path.display()))
}
}
pub fn resolve_server_config(
flag_url: Option<String>,
flag_key: Option<String>,
flag_project: Option<String>,
file_config: &BaselineServerConfig,
) -> ResolvedServerConfig {
ResolvedServerConfig {
url: flag_url.or_else(|| file_config.resolved_url()),
api_key: flag_key.or_else(|| file_config.resolved_api_key()),
project: flag_project.or_else(|| file_config.resolved_project()),
fallback_to_local: file_config.fallback_to_local,
}
}