use crate::config::error::LoadError;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
pub struct Models {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub adapter: Option<PathBuf>,
}
impl Models {
pub fn load(path: &Path) -> Result<Self, LoadError> {
let raw = fs::read_to_string(path).map_err(|source| LoadError::Io {
path: path.to_path_buf(),
source,
})?;
let parsed: Option<Self> =
serde_yaml_ng::from_str(&raw).map_err(|source| LoadError::Yaml {
path: path.to_path_buf(),
source,
})?;
Ok(parsed.unwrap_or_default())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
fn write_yaml(s: &str) -> NamedTempFile {
let mut f = NamedTempFile::new().unwrap();
f.write_all(s.as_bytes()).unwrap();
f
}
#[test]
fn parses_adapter_override() {
let f = write_yaml("adapter: /usr/local/bin/bz\n");
let m = Models::load(f.path()).unwrap();
assert_eq!(m.adapter.as_deref(), Some(Path::new("/usr/local/bin/bz")));
}
#[test]
fn comments_only_file_parses_as_default() {
let f = write_yaml("# no override; `bz` on PATH governs\n");
let m = Models::load(f.path()).unwrap();
assert!(m.adapter.is_none());
}
#[test]
fn retired_models_table_is_ignored() {
let f = write_yaml(
"models:\n m:\n provider: p\n model_id: m\n \
capabilities: []\n context_window: 1\n",
);
let m = Models::load(f.path()).unwrap();
assert!(m.adapter.is_none());
}
#[test]
fn surfaces_yaml_parse_errors() {
let f = write_yaml("not: [valid: yaml");
let err = Models::load(f.path()).unwrap_err();
assert!(matches!(err, LoadError::Yaml { .. }));
}
#[test]
fn surfaces_io_errors() {
let err = Models::load(Path::new("/no/such/models.yaml")).unwrap_err();
assert!(matches!(err, LoadError::Io { .. }));
}
}