优化: R-PD-10 commands err_str helper 统一错误格式化(87 处)

mod.rs 加 pub fn err_str<E: ToString>,commands/ 10 文件 87 处 .map_err(|e| e.to_string()) → .map_err(err_str),残留 0(10 处复杂表达式 e 用于 format!/anyhow 保留)。行为零变化,统一入口为未来加日志/分类留点。批5,cargo workspace 0
This commit is contained in:
2026-06-15 05:45:04 +08:00
parent fddca9daf1
commit 892a642bd4
11 changed files with 105 additions and 94 deletions

View File

@@ -10,7 +10,7 @@ use df_core::types::new_id;
use df_storage::models::AiProviderRecord; use df_storage::models::AiProviderRecord;
use crate::state::AppState; use crate::state::AppState;
use crate::commands::now_millis; use crate::commands::{err_str, now_millis};
use super::agentic::{run_agentic_loop, try_continue_agent_loop}; use super::agentic::{run_agentic_loop, try_continue_agent_loop};
use super::audit::audit_finalize; use super::audit::audit_finalize;
@@ -230,7 +230,7 @@ pub async fn ai_chat_clear(state: State<'_, AppState>) -> Result<(), String> {
.ai_conversations .ai_conversations
.clear_messages(&id) .clear_messages(&id)
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
} }
Ok(()) Ok(())
} }
@@ -311,7 +311,7 @@ fn mask_api_key(key: &str) -> String {
/// 列出所有已配置的 AI 提供商is_default 真相源为 DB重启不丢 /// 列出所有已配置的 AI 提供商is_default 真相源为 DB重启不丢
#[tauri::command] #[tauri::command]
pub async fn ai_list_providers(state: State<'_, AppState>) -> Result<Vec<AiProviderRecord>, String> { pub async fn ai_list_providers(state: State<'_, AppState>) -> Result<Vec<AiProviderRecord>, String> {
let mut providers = state.ai_providers.list_all().await.map_err(|e| e.to_string())?; let mut providers = state.ai_providers.list_all().await.map_err(err_str)?;
// IPC 不传明文 api_key(FR-S1):前端编辑用空 apiKey 表示不改,mask 后前端 realm 不持有明文。 // IPC 不传明文 api_key(FR-S1):前端编辑用空 apiKey 表示不改,mask 后前端 realm 不持有明文。
// 迁移后 DB api_key 空 → 从 keyring 取真实密钥再 mask(前端看到 mask 但不持有明文) // 迁移后 DB api_key 空 → 从 keyring 取真实密钥再 mask(前端看到 mask 但不持有明文)
for p in &mut providers { for p in &mut providers {
@@ -339,7 +339,7 @@ pub async fn ai_save_provider(
// 编辑已有提供商时保留原 created_at避免被覆盖 // 编辑已有提供商时保留原 created_at避免被覆盖
let created_at = match &id { let created_at = match &id {
Some(pid) => state.ai_providers.get_by_id(pid).await Some(pid) => state.ai_providers.get_by_id(pid).await
.map_err(|e| e.to_string())? .map_err(err_str)?
.map(|p| p.created_at) .map(|p| p.created_at)
.unwrap_or_else(now_millis), .unwrap_or_else(now_millis),
None => now_millis(), None => now_millis(),
@@ -347,11 +347,11 @@ pub async fn ai_save_provider(
// is_default编辑保留原值新建时若全表尚无默认则设为默认首个自动默认避免无默认可用 // is_default编辑保留原值新建时若全表尚无默认则设为默认首个自动默认避免无默认可用
let is_default = match &id { let is_default = match &id {
Some(pid) => state.ai_providers.get_by_id(pid).await Some(pid) => state.ai_providers.get_by_id(pid).await
.map_err(|e| e.to_string())? .map_err(err_str)?
.map(|p| p.is_default) .map(|p| p.is_default)
.unwrap_or(false), .unwrap_or(false),
None => !state.ai_providers.list_all().await None => !state.ai_providers.list_all().await
.map_err(|e| e.to_string())? .map_err(err_str)?
.iter().any(|p| p.is_default), .iter().any(|p| p.is_default),
}; };
// FR-S1:密钥存 OS keyring,DB api_key 列恒空(不入明文)。 // FR-S1:密钥存 OS keyring,DB api_key 列恒空(不入明文)。
@@ -369,7 +369,7 @@ pub async fn ai_save_provider(
// 兜底:发现未迁移态先即时迁移补密钥,迁移成功后再让下方清 DB 明文(收敛到迁移完成态); // 兜底:发现未迁移态先即时迁移补密钥,迁移成功后再让下方清 DB 明文(收敛到迁移完成态);
// 迁移失败则 Err 阻断保存且 INSERT OR REPLACE 不执行 → DB 明文保留,绝不劣化现状。 // 迁移失败则 Err 阻断保存且 INSERT OR REPLACE 不执行 → DB 明文保留,绝不劣化现状。
let old = state.ai_providers.get_by_id(pid).await let old = state.ai_providers.get_by_id(pid).await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
if let Some(old) = old { if let Some(old) = old {
if !old.api_key.is_empty() if !old.api_key.is_empty()
&& super::secret::get_provider_secret(pid).is_none() && super::secret::get_provider_secret(pid).is_none()
@@ -409,7 +409,7 @@ pub async fn ai_save_provider(
.ai_providers .ai_providers
.insert(record) .insert(record)
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
Ok(id) Ok(id)
} }
@@ -424,18 +424,18 @@ pub async fn ai_set_provider(
.ai_providers .ai_providers
.get_by_id(&provider_id) .get_by_id(&provider_id)
.await .await
.map_err(|e| e.to_string())? .map_err(err_str)?
.ok_or_else(|| format!("提供商不存在: {}", provider_id))?; .ok_or_else(|| format!("提供商不存在: {}", provider_id))?;
// 互斥写 DB目标 is_default=true其余=false。仅写变化的记录。 // 互斥写 DB目标 is_default=true其余=false。仅写变化的记录。
let providers = state.ai_providers.list_all().await.map_err(|e| e.to_string())?; let providers = state.ai_providers.list_all().await.map_err(err_str)?;
for p in &providers { for p in &providers {
let should = p.id == provider_id; let should = p.id == provider_id;
if p.is_default != should { if p.is_default != should {
let mut updated = p.clone(); let mut updated = p.clone();
updated.is_default = should; updated.is_default = should;
updated.updated_at = now_millis(); updated.updated_at = now_millis();
state.ai_providers.update_full(&updated).await.map_err(|e| e.to_string())?; state.ai_providers.update_full(&updated).await.map_err(err_str)?;
} }
} }
@@ -450,7 +450,7 @@ pub async fn ai_delete_provider(
state: State<'_, AppState>, state: State<'_, AppState>,
provider_id: String, provider_id: String,
) -> Result<(), String> { ) -> Result<(), String> {
state.ai_providers.delete(&provider_id).await.map_err(|e| e.to_string())?; state.ai_providers.delete(&provider_id).await.map_err(err_str)?;
// CR-260615-01:DB 已删则清 keyring 残留密钥(失败仅 warn 不阻断——无 DB 消费方, // CR-260615-01:DB 已删则清 keyring 残留密钥(失败仅 warn 不阻断——无 DB 消费方,
// 残留 keyring 不可复活;同 id 复用也不会读到旧密钥,因 set 覆盖写) // 残留 keyring 不可复活;同 id 复用也不会读到旧密钥,因 set 覆盖写)
if let Err(e) = super::secret::delete_provider_secret(&provider_id) { if let Err(e) = super::secret::delete_provider_secret(&provider_id) {
@@ -518,7 +518,7 @@ pub async fn ai_conversation_list(
) -> Result<Vec<serde_json::Value>, String> { ) -> Result<Vec<serde_json::Value>, String> {
let limit = limit.unwrap_or(50); let limit = limit.unwrap_or(50);
let include_archived = include_archived.unwrap_or(false); let include_archived = include_archived.unwrap_or(false);
let records = state.ai_conversations.list_all().await.map_err(|e| e.to_string())?; let records = state.ai_conversations.list_all().await.map_err(err_str)?;
// list_all 已按 created_at DESC(最新在前);默认排除归档 + 截断 limit // list_all 已按 created_at DESC(最新在前);默认排除归档 + 截断 limit
let summaries: Vec<serde_json::Value> = records.iter() let summaries: Vec<serde_json::Value> = records.iter()
.filter(|r| include_archived || !r.archived) .filter(|r| include_archived || !r.archived)
@@ -551,7 +551,7 @@ pub async fn ai_conversation_switch(
conversation_id: String, conversation_id: String,
) -> Result<serde_json::Value, String> { ) -> Result<serde_json::Value, String> {
let record = state.ai_conversations.get_by_id(&conversation_id).await let record = state.ai_conversations.get_by_id(&conversation_id).await
.map_err(|e| e.to_string())? .map_err(err_str)?
.ok_or_else(|| format!("对话不存在: {}", conversation_id))?; .ok_or_else(|| format!("对话不存在: {}", conversation_id))?;
let messages: Vec<ChatMessage> = serde_json::from_str(&record.messages) let messages: Vec<ChatMessage> = serde_json::from_str(&record.messages)
@@ -591,7 +591,7 @@ pub async fn ai_conversation_delete(
state: State<'_, AppState>, state: State<'_, AppState>,
conversation_id: String, conversation_id: String,
) -> Result<(), String> { ) -> Result<(), String> {
state.ai_conversations.delete(&conversation_id).await.map_err(|e| e.to_string())?; state.ai_conversations.delete(&conversation_id).await.map_err(err_str)?;
let mut session = state.ai_session.lock().await; let mut session = state.ai_session.lock().await;
if session.active_conversation_id.as_deref() == Some(&conversation_id) { if session.active_conversation_id.as_deref() == Some(&conversation_id) {
@@ -614,7 +614,7 @@ pub async fn ai_conversation_rename(
return Err("标题不能为空".to_string()); return Err("标题不能为空".to_string());
} }
state.ai_conversations.update_field(&conversation_id, "title", &title) state.ai_conversations.update_field(&conversation_id, "title", &title)
.await.map_err(|e| e.to_string())?; .await.map_err(err_str)?;
Ok(()) Ok(())
} }
@@ -628,7 +628,7 @@ pub async fn ai_conversation_archive(
state.ai_conversations state.ai_conversations
.set_archived(&conversation_id, archived) .set_archived(&conversation_id, archived)
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
Ok(()) Ok(())
} }

View File

@@ -13,6 +13,8 @@ use df_core::types::new_id;
use crate::state::{AppState, ExtractTrigger, LlmConcurrency}; use crate::state::{AppState, ExtractTrigger, LlmConcurrency};
use crate::commands::err_str;
use super::{AiSession}; use super::{AiSession};
/// 按配置构建 embedding provider + model。None = 配置缺失/provider 不存在。 /// 按配置构建 embedding provider + model。None = 配置缺失/provider 不存在。
@@ -240,7 +242,7 @@ pub async fn trigger_extraction_now(state: &AppState) -> Result<bool, String> {
session.active_conversation_id.clone() session.active_conversation_id.clone()
}; };
let conv_id = conv_id.ok_or_else(|| "当前无活跃对话".to_string())?; let conv_id = conv_id.ok_or_else(|| "当前无活跃对话".to_string())?;
let provider_config = super::prompt::get_active_provider(state).await.map_err(|e| e.to_string())?; let provider_config = super::prompt::get_active_provider(state).await.map_err(err_str)?;
let db = state.db.clone(); let db = state.db.clone();
let llm_concurrency = state.llm_concurrency.clone(); let llm_concurrency = state.llm_concurrency.clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {

View File

@@ -2,6 +2,7 @@
use df_storage::models::AiProviderRecord; use df_storage::models::AiProviderRecord;
use crate::commands::err_str;
use crate::state::AppState; use crate::state::AppState;
/// 获取当前活跃提供商配置 /// 获取当前活跃提供商配置
@@ -12,7 +13,7 @@ pub(crate) async fn get_active_provider(state: &AppState) -> Result<AiProviderRe
.ai_providers .ai_providers
.get_by_id(pid) .get_by_id(pid)
.await .await
.map_err(|e| e.to_string())? .map_err(err_str)?
.ok_or_else(|| format!("活跃提供商不存在: {}", pid))?; .ok_or_else(|| format!("活跃提供商不存在: {}", pid))?;
Ok(provider) Ok(provider)
} else { } else {
@@ -22,7 +23,7 @@ pub(crate) async fn get_active_provider(state: &AppState) -> Result<AiProviderRe
.ai_providers .ai_providers
.list_all() .list_all()
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
let default = providers.iter().find(|p| p.is_default).cloned(); let default = providers.iter().find(|p| p.is_default).cloned();
default default
.or_else(|| providers.into_iter().next()) .or_else(|| providers.into_iter().next())

View File

@@ -9,7 +9,7 @@ use df_storage::models::{IdeaRecord, ProjectRecord};
use crate::state::AppState; use crate::state::AppState;
use super::now_millis; use super::{err_str, now_millis};
/// 创建灵感入参 /// 创建灵感入参
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@@ -35,8 +35,8 @@ pub async fn list_ideas(
status: Option<String>, status: Option<String>,
) -> Result<Vec<IdeaRecord>, String> { ) -> Result<Vec<IdeaRecord>, String> {
match status { match status {
Some(s) => state.ideas.query("status", &s).await.map_err(|e| e.to_string()), Some(s) => state.ideas.query("status", &s).await.map_err(err_str),
None => state.ideas.list_all().await.map_err(|e| e.to_string()), None => state.ideas.list_all().await.map_err(err_str),
} }
} }
@@ -66,7 +66,7 @@ pub async fn create_idea(
.ideas .ideas
.insert(record.clone()) .insert(record.clone())
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
Ok(record) Ok(record)
} }
@@ -82,13 +82,13 @@ pub async fn update_idea(
.ideas .ideas
.update_field(&id, &field, &value) .update_field(&id, &field, &value)
.await .await
.map_err(|e| e.to_string()) .map_err(err_str)
} }
/// 删除灵感 /// 删除灵感
#[tauri::command] #[tauri::command]
pub async fn delete_idea(state: State<'_, AppState>, id: String) -> Result<bool, String> { pub async fn delete_idea(state: State<'_, AppState>, id: String) -> Result<bool, String> {
state.ideas.delete(&id).await.map_err(|e| e.to_string()) state.ideas.delete(&id).await.map_err(err_str)
} }
/// 将灵感晋升为项目 — 复用 df-project 领域逻辑创建项目,回写灵感 status=promoted/promoted_to /// 将灵感晋升为项目 — 复用 df-project 领域逻辑创建项目,回写灵感 status=promoted/promoted_to
@@ -101,7 +101,7 @@ pub async fn promote_idea(
.ideas .ideas
.get_by_id(&id) .get_by_id(&id)
.await .await
.map_err(|e| e.to_string())? .map_err(err_str)?
.ok_or_else(|| format!("灵感不存在: {id}"))?; .ok_or_else(|| format!("灵感不存在: {id}"))?;
if record.promoted_to.is_some() { if record.promoted_to.is_some() {
@@ -131,7 +131,7 @@ pub async fn promote_idea(
.projects .projects
.insert(project_record) .insert(project_record)
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
// 回写灵感status=promoted + promoted_toupdate_full 单事务覆盖可变字段) // 回写灵感status=promoted + promoted_toupdate_full 单事务覆盖可变字段)
// 补偿删除:第二步失败时回滚第一步已建的 project,保证最终一致性(非原子,但防项目存留而 // 补偿删除:第二步失败时回滚第一步已建的 project,保证最终一致性(非原子,但防项目存留而
@@ -175,7 +175,7 @@ pub async fn evaluate_idea(
.ideas .ideas
.get_by_id(&id) .get_by_id(&id)
.await .await
.map_err(|e| e.to_string())? .map_err(err_str)?
.ok_or_else(|| format!("灵感不存在: {id}"))?; .ok_or_else(|| format!("灵感不存在: {id}"))?;
let idea = record_to_idea(&record); let idea = record_to_idea(&record);
@@ -186,7 +186,7 @@ pub async fn evaluate_idea(
// 对抗式评估 // 对抗式评估
let eval = df_ideas::adversarial::AdversarialEngine::evaluate(&idea) let eval = df_ideas::adversarial::AdversarialEngine::evaluate(&idea)
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
// 组装前端扁平结构(与 Ideas.vue 的 AdversarialEval interface 对齐) // 组装前端扁平结构(与 Ideas.vue 的 AdversarialEval interface 对齐)
let positive_strength = eval.positive.confidence; let positive_strength = eval.positive.confidence;
@@ -243,7 +243,7 @@ pub async fn evaluate_idea(
.ideas .ideas
.update_full(&updated) .update_full(&updated)
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
Ok(updated) Ok(updated)
} }

View File

@@ -12,7 +12,7 @@ use df_storage::models::{KnowledgeEventRecord, KnowledgeRecord};
use crate::state::{AppState, KnowledgeConfig}; use crate::state::{AppState, KnowledgeConfig};
use super::knowledge_timeline::KnowledgeTimeline; use super::knowledge_timeline::KnowledgeTimeline;
use super::now_millis; use super::{err_str, now_millis};
use serde::Serialize; use serde::Serialize;
/// 创建知识入参 /// 创建知识入参
@@ -76,10 +76,10 @@ pub async fn knowledge_list(
) -> Result<Vec<KnowledgeRecord>, String> { ) -> Result<Vec<KnowledgeRecord>, String> {
match status { match status {
// 显式查 archived 时原样返回(含归档项) // 显式查 archived 时原样返回(含归档项)
Some(s) if s == "archived" => state.knowledge.list_by_status("archived").await.map_err(|e| e.to_string()), Some(s) if s == "archived" => state.knowledge.list_by_status("archived").await.map_err(err_str),
Some(s) => state.knowledge.list_by_status(&s).await.map_err(|e| e.to_string()), Some(s) => state.knowledge.list_by_status(&s).await.map_err(err_str),
// 默认:列出非 archived 的全部(单查询 status != 'archived') // 默认:列出非 archived 的全部(单查询 status != 'archived')
None => state.knowledge.list_non_archived().await.map_err(|e| e.to_string()), None => state.knowledge.list_non_archived().await.map_err(err_str),
} }
} }
@@ -93,7 +93,7 @@ pub async fn knowledge_get(
.knowledge .knowledge
.get_by_id(&id) .get_by_id(&id)
.await .await
.map_err(|e| e.to_string())? .map_err(err_str)?
.ok_or_else(|| format!("知识不存在: {id}")) .ok_or_else(|| format!("知识不存在: {id}"))
} }
@@ -108,7 +108,7 @@ pub async fn knowledge_search(
.knowledge .knowledge
.search(&input.query, input.kind.as_deref(), limit) .search(&input.query, input.kind.as_deref(), limit)
.await .await
.map_err(|e| e.to_string()) .map_err(err_str)
} }
/// 创建知识(始终 candidate 状态) — MCP create tool /// 创建知识(始终 candidate 状态) — MCP create tool
@@ -138,7 +138,7 @@ pub async fn knowledge_create(
.knowledge .knowledge
.insert(record.clone()) .insert(record.clone())
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
// 生命线:手动录入产生(fire-and-forget) // 生命线:手动录入产生(fire-and-forget)
KnowledgeTimeline::new(&state.db) KnowledgeTimeline::new(&state.db)
.record_created(&record.id, "manual") .record_created(&record.id, "manual")
@@ -161,7 +161,7 @@ pub async fn knowledge_update_status(
.knowledge .knowledge
.get_by_id(&id) .get_by_id(&id)
.await .await
.map_err(|e| e.to_string())? .map_err(err_str)?
.ok_or_else(|| format!("知识不存在: {id}"))?; .ok_or_else(|| format!("知识不存在: {id}"))?;
let old_status = record.status.clone(); let old_status = record.status.clone();
@@ -179,7 +179,7 @@ pub async fn knowledge_update_status(
.knowledge .knowledge
.update_full(&updated) .update_full(&updated)
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
// 生命线:状态变更(fire-and-forget;to=published=审核通过,to=archived=归档) // 生命线:状态变更(fire-and-forget;to=published=审核通过,to=archived=归档)
KnowledgeTimeline::new(&state.db) KnowledgeTimeline::new(&state.db)
@@ -205,7 +205,7 @@ pub async fn knowledge_record_reuse(
.knowledge .knowledge
.increment_reuse_count(&id) .increment_reuse_count(&id)
.await .await
.map_err(|e| e.to_string()) .map_err(err_str)
} }
/// 审核收件箱 — 列出 candidate(按 confidence 语义排序) /// 审核收件箱 — 列出 candidate(按 confidence 语义排序)
@@ -217,7 +217,7 @@ pub async fn knowledge_list_candidates(
.knowledge .knowledge
.list_by_status("candidate") .list_by_status("candidate")
.await .await
.map_err(|e| e.to_string()) .map_err(err_str)
} }
/// 归档(软删除) — UPDATE status='archived' /// 归档(软删除) — UPDATE status='archived'
@@ -300,13 +300,13 @@ pub async fn knowledge_get_detail(
.knowledge .knowledge
.get_by_id(&id) .get_by_id(&id)
.await .await
.map_err(|e| e.to_string())? .map_err(err_str)?
.ok_or_else(|| format!("知识不存在: {id}"))?; .ok_or_else(|| format!("知识不存在: {id}"))?;
let events = state let events = state
.knowledge_events .knowledge_events
.list_by_knowledge(&id) .list_by_knowledge(&id)
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
Ok(KnowledgeDetailPayload { knowledge, events }) Ok(KnowledgeDetailPayload { knowledge, events })
} }
@@ -321,7 +321,7 @@ pub async fn knowledge_update(
.knowledge .knowledge
.get_by_id(&id) .get_by_id(&id)
.await .await
.map_err(|e| e.to_string())? .map_err(err_str)?
.ok_or_else(|| format!("知识不存在: {id}"))?; .ok_or_else(|| format!("知识不存在: {id}"))?;
if let Some(v) = input.title { if let Some(v) = input.title {
record.title = v; record.title = v;
@@ -344,7 +344,7 @@ pub async fn knowledge_update(
.knowledge .knowledge
.update_full(&record) .update_full(&record)
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
Ok(record) Ok(record)
} }
@@ -363,13 +363,13 @@ pub async fn knowledge_events(
.knowledge_events .knowledge_events
.list_by_knowledge_type(&knowledge_id, &et, limit) .list_by_knowledge_type(&knowledge_id, &et, limit)
.await .await
.map_err(|e| e.to_string()) .map_err(err_str)
} }
None => state None => state
.knowledge_events .knowledge_events
.list_by_knowledge(&knowledge_id) .list_by_knowledge(&knowledge_id)
.await .await
.map_err(|e| e.to_string()), .map_err(err_str),
} }
} }

View File

@@ -12,7 +12,7 @@ use df_storage::crud::KnowledgeEventsRepo;
use df_storage::db::Database; use df_storage::db::Database;
use df_storage::models::KnowledgeEventRecord; use df_storage::models::KnowledgeEventRecord;
use super::now_millis; use super::{err_str, now_millis};
/// 事件类型常量(生命线节点;归档复用 status_changed 的 to=archived,不单列) /// 事件类型常量(生命线节点;归档复用 status_changed 的 to=archived,不单列)
pub const EVENT_CREATED: &str = "created"; pub const EVENT_CREATED: &str = "created";
@@ -57,7 +57,7 @@ impl KnowledgeTimeline {
KnowledgeEventsRepo::new(&self.db) KnowledgeEventsRepo::new(&self.db)
.insert(record) .insert(record)
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
Ok(()) Ok(())
} }

View File

@@ -17,3 +17,9 @@ pub mod workflow;
pub(crate) fn now_millis() -> String { pub(crate) fn now_millis() -> String {
df_core::now_millis().to_string() df_core::now_millis().to_string()
} }
/// .map_err 错误格式化统一入口 — 当前行为零变化(仅 e.to_string()),
/// 集中收敛为后续加日志/分类/类型名等扩展留点,替代各处散落的 .map_err(|e| e.to_string())。
pub fn err_str<E: std::string::ToString>(e: E) -> String {
e.to_string()
}

View File

@@ -12,7 +12,7 @@ use df_storage::models::ProjectRecord;
use crate::state::AppState; use crate::state::AppState;
use super::now_millis; use super::{err_str, now_millis};
/// 创建项目入参 /// 创建项目入参
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@@ -32,7 +32,7 @@ pub struct CreateProjectInput {
/// 列出未删除项目(过滤回收站) /// 列出未删除项目(过滤回收站)
#[tauri::command] #[tauri::command]
pub async fn list_projects(state: State<'_, AppState>) -> Result<Vec<ProjectRecord>, String> { pub async fn list_projects(state: State<'_, AppState>) -> Result<Vec<ProjectRecord>, String> {
state.projects.list_active().await.map_err(|e| e.to_string()) state.projects.list_active().await.map_err(err_str)
} }
/// 创建项目,返回完整记录 /// 创建项目,返回完整记录
@@ -59,9 +59,9 @@ pub async fn create_project(
let root = std::path::PathBuf::from(p); let root = std::path::PathBuf::from(p);
let detected = tokio::task::spawn_blocking(move || detect_stack(&root)) let detected = tokio::task::spawn_blocking(move || detect_stack(&root))
.await .await
.map_err(|e| e.to_string())? .map_err(err_str)?
.map_err(|e| e.to_string())?; .map_err(err_str)?;
serde_json::to_string(&detected).map_err(|e| e.to_string())? serde_json::to_string(&detected).map_err(err_str)?
} }
}; };
(Some(p.to_string()), Some(stack_json)) (Some(p.to_string()), Some(stack_json))
@@ -85,7 +85,7 @@ pub async fn create_project(
.projects .projects
.insert(record.clone()) .insert(record.clone())
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
Ok(record) Ok(record)
} }
@@ -153,14 +153,14 @@ pub async fn import_project(
let stack_json = match want_stack.as_deref().map(str::trim).filter(|s| !s.is_empty()) { let stack_json = match want_stack.as_deref().map(str::trim).filter(|s| !s.is_empty()) {
Some(s) => s.to_string(), Some(s) => s.to_string(),
None => { None => {
let detected = detect_stack(&root).map_err(|e| e.to_string())?; let detected = detect_stack(&root).map_err(err_str)?;
serde_json::to_string(&detected).map_err(|e| e.to_string())? serde_json::to_string(&detected).map_err(err_str)?
} }
}; };
Ok((name, description, stack_json)) Ok((name, description, stack_json))
}) })
.await .await
.map_err(|e| e.to_string())??; .map_err(err_str)??;
let now = now_millis(); let now = now_millis();
let record = ProjectRecord { let record = ProjectRecord {
@@ -178,7 +178,7 @@ pub async fn import_project(
.projects .projects
.insert(record.clone()) .insert(record.clone())
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
Ok(record) Ok(record)
} }
@@ -192,7 +192,7 @@ pub async fn get_project(
.projects .projects
.get_by_id(&id) .get_by_id(&id)
.await .await
.map_err(|e| e.to_string()) .map_err(err_str)
} }
/// 更新项目单个字段(字段名走 df-storage 白名单校验) /// 更新项目单个字段(字段名走 df-storage 白名单校验)
@@ -207,13 +207,13 @@ pub async fn update_project(
.projects .projects
.update_field(&id, &field, &value) .update_field(&id, &field, &value)
.await .await
.map_err(|e| e.to_string()) .map_err(err_str)
} }
/// 删除项目(软删 → 回收站,可恢复) /// 删除项目(软删 → 回收站,可恢复)
#[tauri::command] #[tauri::command]
pub async fn delete_project(state: State<'_, AppState>, id: String) -> Result<bool, String> { pub async fn delete_project(state: State<'_, AppState>, id: String) -> Result<bool, String> {
state.projects.soft_delete(&id).await.map_err(|e| e.to_string()) state.projects.soft_delete(&id).await.map_err(err_str)
} }
/// 列出回收站项目(deleted_at IS NOT NULL) /// 列出回收站项目(deleted_at IS NOT NULL)
@@ -221,13 +221,13 @@ pub async fn delete_project(state: State<'_, AppState>, id: String) -> Result<bo
pub async fn list_deleted_projects( pub async fn list_deleted_projects(
state: State<'_, AppState>, state: State<'_, AppState>,
) -> Result<Vec<ProjectRecord>, String> { ) -> Result<Vec<ProjectRecord>, String> {
state.projects.list_deleted().await.map_err(|e| e.to_string()) state.projects.list_deleted().await.map_err(err_str)
} }
/// 恢复项目(从回收站还原,清 deleted_at) /// 恢复项目(从回收站还原,清 deleted_at)
#[tauri::command] #[tauri::command]
pub async fn restore_project(state: State<'_, AppState>, id: String) -> Result<bool, String> { pub async fn restore_project(state: State<'_, AppState>, id: String) -> Result<bool, String> {
state.projects.restore(&id).await.map_err(|e| e.to_string()) state.projects.restore(&id).await.map_err(err_str)
} }
/// 彻底删除项目(级联物理删 branches/releases/tasks,不可恢复) /// 彻底删除项目(级联物理删 branches/releases/tasks,不可恢复)
@@ -237,7 +237,7 @@ pub async fn purge_project(state: State<'_, AppState>, id: String) -> Result<boo
.projects .projects
.purge_with_descendants(&id) .purge_with_descendants(&id)
.await .await
.map_err(|e| e.to_string()) .map_err(err_str)
} }
// ============================================================ // ============================================================
@@ -258,16 +258,16 @@ async fn find_binding_conflict(
.projects .projects
.find_path_conflict(&norm, exclude_id) .find_path_conflict(&norm, exclude_id)
.await .await
.map_err(|e| e.to_string()) .map_err(err_str)
} }
/// 探测目录技术栈(前端选目录后实时预览) /// 探测目录技术栈(前端选目录后实时预览)
#[tauri::command] #[tauri::command]
pub async fn scan_project_stack(path: String) -> Result<Vec<String>, String> { pub async fn scan_project_stack(path: String) -> Result<Vec<String>, String> {
let root = std::path::PathBuf::from(&path); let root = std::path::PathBuf::from(&path);
tokio::task::spawn_blocking(move || detect_stack(&root).map_err(|e| e.to_string())) tokio::task::spawn_blocking(move || detect_stack(&root).map_err(err_str))
.await .await
.map_err(|e| e.to_string())? .map_err(err_str)?
} }
/// 检查目录是否已被其他项目绑定(防重复绑定)。返回占用项目(若有)。 /// 检查目录是否已被其他项目绑定(防重复绑定)。返回占用项目(若有)。
@@ -298,24 +298,24 @@ pub async fn relocate_project_path(
let root = std::path::PathBuf::from(&new_path); let root = std::path::PathBuf::from(&new_path);
let stack = tokio::task::spawn_blocking(move || detect_stack(&root)) let stack = tokio::task::spawn_blocking(move || detect_stack(&root))
.await .await
.map_err(|e| e.to_string())? .map_err(err_str)?
.map_err(|e| e.to_string())?; .map_err(err_str)?;
let stack_json = serde_json::to_string(&stack).map_err(|e| e.to_string())?; let stack_json = serde_json::to_string(&stack).map_err(err_str)?;
state state
.projects .projects
.update_field(&id, "path", &new_path) .update_field(&id, "path", &new_path)
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
state state
.projects .projects
.update_field(&id, "stack", &stack_json) .update_field(&id, "stack", &stack_json)
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
state state
.projects .projects
.get_by_id(&id) .get_by_id(&id)
.await .await
.map_err(|e| e.to_string())? .map_err(err_str)?
.ok_or_else(|| "项目不存在".to_string()) .ok_or_else(|| "项目不存在".to_string())
} }
@@ -364,11 +364,11 @@ pub async fn scan_project_with_ai(
Ok::<_, anyhow::Error>((stack, sample)) Ok::<_, anyhow::Error>((stack, sample))
}) })
.await .await
.map_err(|e| e.to_string())? .map_err(err_str)?
.map_err(|e| e.to_string())?; .map_err(err_str)?;
// 2. 取默认 provider(优先 is_default,否则首个) // 2. 取默认 provider(优先 is_default,否则首个)
let providers = state.ai_providers.list_all().await.map_err(|e| e.to_string())?; let providers = state.ai_providers.list_all().await.map_err(err_str)?;
let pc = providers let pc = providers
.iter() .iter()
.find(|p| p.is_default) .find(|p| p.is_default)

View File

@@ -9,13 +9,15 @@ use tauri::State;
use crate::state::AppState; use crate::state::AppState;
use super::err_str;
/// 取单个 key 的值(JSON 字符串),不存在返回 None /// 取单个 key 的值(JSON 字符串),不存在返回 None
#[tauri::command] #[tauri::command]
pub async fn settings_get( pub async fn settings_get(
state: State<'_, AppState>, state: State<'_, AppState>,
key: String, key: String,
) -> Result<Option<String>, String> { ) -> Result<Option<String>, String> {
state.settings.get(&key).await.map_err(|e| e.to_string()) state.settings.get(&key).await.map_err(err_str)
} }
/// 写 key/value(`INSERT OR REPLACE`),刷新 updated_at /// 写 key/value(`INSERT OR REPLACE`),刷新 updated_at
@@ -25,7 +27,7 @@ pub async fn settings_set(
key: String, key: String,
value: String, value: String,
) -> Result<bool, String> { ) -> Result<bool, String> {
state.settings.set(&key, &value).await.map_err(|e| e.to_string()) state.settings.set(&key, &value).await.map_err(err_str)
} }
/// 取全部 key/value(前端启动时一次性拉回恢复偏好) /// 取全部 key/value(前端启动时一次性拉回恢复偏好)
@@ -33,7 +35,7 @@ pub async fn settings_set(
pub async fn settings_get_all( pub async fn settings_get_all(
state: State<'_, AppState>, state: State<'_, AppState>,
) -> Result<HashMap<String, String>, String> { ) -> Result<HashMap<String, String>, String> {
let rows = state.settings.get_all().await.map_err(|e| e.to_string())?; let rows = state.settings.get_all().await.map_err(err_str)?;
Ok(rows.into_iter().collect()) Ok(rows.into_iter().collect())
} }
@@ -43,5 +45,5 @@ pub async fn settings_delete(
state: State<'_, AppState>, state: State<'_, AppState>,
key: String, key: String,
) -> Result<bool, String> { ) -> Result<bool, String> {
state.settings.delete(&key).await.map_err(|e| e.to_string()) state.settings.delete(&key).await.map_err(err_str)
} }

View File

@@ -8,7 +8,7 @@ use df_storage::models::TaskRecord;
use crate::state::AppState; use crate::state::AppState;
use super::now_millis; use super::{err_str, now_millis};
/// 创建任务入参 /// 创建任务入参
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@@ -37,7 +37,7 @@ pub async fn list_tasks(
Some(pid) => state.tasks.query("project_id", &pid).await, Some(pid) => state.tasks.query("project_id", &pid).await,
None => state.tasks.list_all().await, None => state.tasks.list_all().await,
}; };
result.map_err(|e| e.to_string()) result.map_err(err_str)
} }
/// 按 id 查任务,找不到返回 Err(供前端详情页) /// 按 id 查任务,找不到返回 Err(供前端详情页)
@@ -50,7 +50,7 @@ pub async fn get_task_by_id(
.tasks .tasks
.get_by_id(&id) .get_by_id(&id)
.await .await
.map_err(|e| e.to_string())? .map_err(err_str)?
.ok_or_else(|| format!("任务 {} 不存在", id)) .ok_or_else(|| format!("任务 {} 不存在", id))
} }
@@ -79,7 +79,7 @@ pub async fn create_task(
.tasks .tasks
.insert(record.clone()) .insert(record.clone())
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
Ok(record) Ok(record)
} }
@@ -104,11 +104,11 @@ pub async fn update_task(
.tasks .tasks
.update_field(&id, &field, &value) .update_field(&id, &field, &value)
.await .await
.map_err(|e| e.to_string()) .map_err(err_str)
} }
/// 删除任务 /// 删除任务
#[tauri::command] #[tauri::command]
pub async fn delete_task(state: State<'_, AppState>, id: String) -> Result<bool, String> { pub async fn delete_task(state: State<'_, AppState>, id: String) -> Result<bool, String> {
state.tasks.delete(&id).await.map_err(|e| e.to_string()) state.tasks.delete(&id).await.map_err(err_str)
} }

View File

@@ -16,7 +16,7 @@ use df_workflow::executor::DagExecutor;
use crate::state::AppState; use crate::state::AppState;
use super::now_millis; use super::{err_str, now_millis};
/// 转发到前端的事件载荷 /// 转发到前端的事件载荷
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
@@ -41,11 +41,11 @@ pub async fn run_workflow(
config: serde_json::Value, config: serde_json::Value,
) -> Result<String, String> { ) -> Result<String, String> {
// 1. 先构建运行时 DAG校验失败直接返回不落库 // 1. 先构建运行时 DAG校验失败直接返回不落库
let runtime_dag = state.registry.build_dag(&dag).map_err(|e| e.to_string())?; let runtime_dag = state.registry.build_dag(&dag).map_err(err_str)?;
// 2. 写入执行记录status=running // 2. 写入执行记录status=running
let execution_id = new_id(); let execution_id = new_id();
let dag_json = serde_json::to_string(&dag).map_err(|e| e.to_string())?; let dag_json = serde_json::to_string(&dag).map_err(err_str)?;
let record = WorkflowRecord { let record = WorkflowRecord {
id: execution_id.clone(), id: execution_id.clone(),
name, name,
@@ -61,7 +61,7 @@ pub async fn run_workflow(
.workflows .workflows
.insert(record) .insert(record)
.await .await
.map_err(|e| e.to_string())?; .map_err(err_str)?;
// 3. 订阅事件总线,把事件转发给前端(收到完成/失败事件后退出) // 3. 订阅事件总线,把事件转发给前端(收到完成/失败事件后退出)
let mut rx = state.event_bus.subscribe(); let mut rx = state.event_bus.subscribe();
@@ -173,7 +173,7 @@ pub async fn run_workflow(
pub async fn list_workflow_executions( pub async fn list_workflow_executions(
state: State<'_, AppState>, state: State<'_, AppState>,
) -> Result<Vec<WorkflowRecord>, String> { ) -> Result<Vec<WorkflowRecord>, String> {
state.workflows.list_all().await.map_err(|e| e.to_string()) state.workflows.list_all().await.map_err(err_str)
} }
/// 按 ID 查询工作流执行记录 /// 按 ID 查询工作流执行记录
@@ -186,7 +186,7 @@ pub async fn get_workflow_execution(
.workflows .workflows
.get_by_id(&id) .get_by_id(&id)
.await .await
.map_err(|e| e.to_string()) .map_err(err_str)
} }
/// 发送人工审批响应 /// 发送人工审批响应