use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Preset {
pub name: String,
pub description: String,
#[serde(default)]
pub tags: Vec<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub mappings: HashMap<String, String>,
}
impl Preset {
pub fn new(name: String, description: String, mappings: HashMap<String, String>) -> Self {
let now = Utc::now();
Self {
name,
description,
tags: Vec::new(),
created_at: now,
updated_at: now,
mappings,
}
}
pub fn validate(
&self,
available_models: &std::collections::HashSet<String>,
) -> anyhow::Result<()> {
if !is_valid_preset_name(&self.name) {
anyhow::bail!("预设名称格式无效: {}", self.name);
}
if self.description.len() > 512 {
anyhow::bail!("预设描述过长(最多 512 字符)");
}
if self.tags.len() > 10 {
anyhow::bail!("标签数量过多(最多 10 个)");
}
for tag in &self.tags {
if tag.len() > 32 {
anyhow::bail!("标签过长: {}(最多 32 字符)", tag);
}
}
if self.mappings.is_empty() {
anyhow::bail!("预设必须包含至少一个工具映射");
}
for model_name in self.mappings.values() {
if !available_models.contains(model_name) {
anyhow::bail!("模型配置不存在: {}", model_name);
}
}
if self.updated_at < self.created_at {
anyhow::bail!("更新时间不能早于创建时间");
}
Ok(())
}
}
pub fn is_valid_preset_name(name: &str) -> bool {
if name.is_empty() || name.len() > 64 {
return false;
}
name.chars()
.all(|c| c.is_alphanumeric() || c == '-' || c == '_')
}
pub fn is_valid_version(version: &str) -> bool {
let parts: Vec<&str> = version.split('.').collect();
if parts.len() != 3 {
return false;
}
parts[0].parse::<u32>().is_ok()
&& parts[1].parse::<u32>().is_ok()
&& (parts[2].parse::<u32>().is_ok() || parts[2].starts_with('-'))
}