use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("HTTP request failed: {0}")]
Http(#[from] reqwest::Error),
#[error("API error (status {status}): {message}")]
Api { status: u16, message: String },
#[error("Invalid input: {0}")]
InvalidInput(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Model {
pub id: Option<String>,
pub name: Option<String>,
pub slug: Option<String>,
pub provider: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Benchmark {
pub id: Option<String>,
pub name: Option<String>,
pub slug: Option<String>,
pub category: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComparisonResult {
pub models: Option<Vec<serde_json::Value>>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
pub struct BenchGecko {
base_url: String,
client: reqwest::blocking::Client,
}
impl BenchGecko {
pub fn new() -> Self {
Self::with_base_url("https://benchgecko.ai")
}
pub fn with_base_url(base_url: &str) -> Self {
let client = reqwest::blocking::Client::builder()
.user_agent("benchgecko-rust/0.1.0")
.timeout(std::time::Duration::from_secs(30))
.build()
.expect("Failed to build HTTP client");
Self {
base_url: base_url.trim_end_matches('/').to_string(),
client,
}
}
pub fn models(&self) -> Result<Vec<Model>, Error> {
let url = format!("{}/api/v1/models", self.base_url);
let resp = self.client.get(&url).send()?;
if !resp.status().is_success() {
return Err(Error::Api {
status: resp.status().as_u16(),
message: resp.text().unwrap_or_default(),
});
}
Ok(resp.json()?)
}
pub fn benchmarks(&self) -> Result<Vec<Benchmark>, Error> {
let url = format!("{}/api/v1/benchmarks", self.base_url);
let resp = self.client.get(&url).send()?;
if !resp.status().is_success() {
return Err(Error::Api {
status: resp.status().as_u16(),
message: resp.text().unwrap_or_default(),
});
}
Ok(resp.json()?)
}
pub fn compare(&self, models: &[&str]) -> Result<ComparisonResult, Error> {
if models.len() < 2 {
return Err(Error::InvalidInput(
"At least 2 models are required for comparison".to_string(),
));
}
let url = format!("{}/api/v1/compare", self.base_url);
let resp = self
.client
.get(&url)
.query(&[("models", models.join(","))])
.send()?;
if !resp.status().is_success() {
return Err(Error::Api {
status: resp.status().as_u16(),
message: resp.text().unwrap_or_default(),
});
}
Ok(resp.json()?)
}
}
impl Default for BenchGecko {
fn default() -> Self {
Self::new()
}
}