use super::{presets, ProviderPreset};
#[derive(Debug, Clone)]
pub struct PresetCatalog {
list: Vec<ProviderPreset>,
}
impl Default for PresetCatalog {
fn default() -> Self {
Self::new()
}
}
impl PresetCatalog {
pub fn new() -> Self {
Self {
list: presets().to_vec(),
}
}
pub fn empty() -> Self {
Self { list: Vec::new() }
}
pub fn remove(mut self, keys: &[&str]) -> Self {
self.list.retain(|p| !keys.contains(&p.key));
self
}
pub fn retain(mut self, keep: impl Fn(&ProviderPreset) -> bool) -> Self {
self.list.retain(|p| keep(p));
self
}
pub fn map(mut self, f: impl Fn(ProviderPreset) -> ProviderPreset) -> Self {
self.list = self.list.into_iter().map(f).collect();
self
}
pub fn extend(mut self, extra: &[ProviderPreset]) -> Self {
for p in extra {
if let Some(slot) = self.list.iter_mut().find(|q| q.key == p.key) {
*slot = *p;
continue;
}
let same_group = self
.list
.iter()
.rposition(|q| q.kind == p.kind && q.group_key == p.group_key);
let same_kind = self.list.iter().rposition(|q| q.kind == p.kind);
match same_group.or(same_kind) {
Some(i) => self.list.insert(i + 1, *p),
None => self.list.push(*p),
}
}
self
}
pub fn build(self) -> Vec<ProviderPreset> {
self.list
}
pub fn vendors(&self, allow_kinds: &[crate::kind::Kind]) -> Vec<super::Vendor> {
super::vendors_in(&self.list, allow_kinds)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kind::Kind;
use crate::preset::{ModelOption, GROUP_CHINA, GROUP_INTERNATIONAL};
static LOCAL: &[ProviderPreset] = &[
ProviderPreset::new(
"local_cn",
Kind::Chat,
"本地国内",
Some("https://cn.example/v1"),
)
.with_group(GROUP_CHINA)
.with_models("m1", &[ModelOption::plain("m1")]),
ProviderPreset::new(
"local_intl",
Kind::Chat,
"本地国际",
Some("https://intl.example/v1"),
)
.with_group(GROUP_INTERNATIONAL),
];
#[test]
fn add_inserts_into_matching_group_keeping_it_contiguous() {
let list = PresetCatalog::new().extend(LOCAL).build();
let pos = |k: &str| list.iter().position(|p| p.key == k).unwrap();
assert_eq!(list[pos("local_cn") - 1].group_key, GROUP_CHINA.0);
assert_eq!(list[pos("local_intl") - 1].group_key, GROUP_INTERNATIONAL.0);
let mut seen: Vec<&str> = Vec::new();
let mut prev = "";
for p in list.iter().filter(|p| p.kind == Kind::Chat) {
if p.group_key != prev {
assert!(!seen.contains(&p.group_key), "分组 {} 被拆开", p.group_key);
seen.push(p.group_key);
prev = p.group_key;
}
}
}
#[test]
fn add_with_same_key_overrides_in_place() {
static OVERRIDE: &[ProviderPreset] = &[ProviderPreset::new(
"deepseek",
Kind::Chat,
"我的 DeepSeek",
Some("https://api.deepseek.com/v1"),
)
.with_group(GROUP_CHINA)];
let before = PresetCatalog::new().build();
let after = PresetCatalog::new().extend(OVERRIDE).build();
assert_eq!(before.len(), after.len(), "覆盖不增加条数");
let i = before.iter().position(|p| p.key == "deepseek").unwrap();
assert_eq!(after[i].label, "我的 DeepSeek", "原位覆盖");
}
#[test]
fn vendors_follow_customized_catalog() {
let cat = PresetCatalog::new().remove(&["deepseek"]).extend(LOCAL);
let vs = cat.vendors(&[Kind::Chat]);
assert!(vs.iter().any(|v| v.id == "local_cn"));
assert!(!vs.iter().any(|v| v.id == "deepseek"));
assert_eq!(
super::super::vendors_in(&cat.clone().build(), &[Kind::Chat]).len(),
vs.len()
);
}
#[test]
fn remove_and_retain() {
let list = PresetCatalog::new()
.remove(&["deepseek", "不存在的 key"])
.retain(|p| p.kind == Kind::Chat)
.build();
assert!(!list.iter().any(|p| p.key == "deepseek"));
assert!(list.iter().all(|p| p.kind == Kind::Chat));
}
#[test]
fn default_extra_and_builder_defaults() {
const P: ProviderPreset = ProviderPreset::new("x", Kind::Chat, "X", None)
.with_default_extra(&[("video_api", "newapi")]);
assert_eq!(P.vendor_id, "x", "vendor_id 缺省取 key");
assert_eq!(P.default_extra, &[("video_api", "newapi")]);
assert!(P.models.is_empty() && P.model.is_empty());
}
}