fix: 修复工作流模板JSON解析错误
- 添加 WorkflowType 的 From<String> 实现,支持从字符串转换 - 修复 workflow_template_repository 中的JSON字段解析,处理空字符串情况 - 为所有JSON字段(comfyui_workflow_json, ui_config_json, execution_config_json等)添加空字符串检查 - 添加详细的数据库调试测试,帮助诊断JSON解析问题 - 修复 WorkflowTemplateFilter 添加缺失的 search_term 字段 主要修复: 1. JSON解析:当JSON字段为空字符串时,返回空对象而不是解析错误 2. 类型转换:WorkflowType 现在可以从数据库字符串直接转换 3. 错误处理:改进了JSON解析的错误处理逻辑 4. 测试工具:添加了详细的数据库内容检查和逐字段解析测试 这些修复解决了前端 WorkflowList.tsx 中的 'premature end of input' JSON解析错误。
This commit is contained in:
parent
4af81a662d
commit
2d3c44d5e9
|
|
@ -474,42 +474,65 @@ impl WorkflowTemplateRepository {
|
||||||
|
|
||||||
// 解析工作流类型
|
// 解析工作流类型
|
||||||
let type_str: String = row.get("type")?;
|
let type_str: String = row.get("type")?;
|
||||||
let workflow_type: WorkflowType = serde_json::from_str(&type_str)
|
let workflow_type: WorkflowType = WorkflowType::from(type_str);
|
||||||
.map_err(|e| rusqlite::Error::FromSqlConversionFailure(
|
|
||||||
0, rusqlite::types::Type::Text, Box::new(e)
|
|
||||||
))?;
|
|
||||||
|
|
||||||
let description: Option<String> = row.get("description")?;
|
let description: Option<String> = row.get("description")?;
|
||||||
|
|
||||||
// 解析JSON字段
|
// 解析JSON字段,处理空字符串的情况
|
||||||
let comfyui_workflow_json_str: String = row.get("comfyui_workflow_json")?;
|
let comfyui_workflow_json_str: String = row.get("comfyui_workflow_json")?;
|
||||||
let comfyui_workflow_json: serde_json::Value = serde_json::from_str(&comfyui_workflow_json_str)
|
let comfyui_workflow_json: serde_json::Value = if comfyui_workflow_json_str.trim().is_empty() {
|
||||||
.map_err(|e| rusqlite::Error::FromSqlConversionFailure(
|
serde_json::Value::Object(serde_json::Map::new())
|
||||||
0, rusqlite::types::Type::Text, Box::new(e)
|
} else {
|
||||||
))?;
|
serde_json::from_str(&comfyui_workflow_json_str)
|
||||||
|
.map_err(|e| rusqlite::Error::FromSqlConversionFailure(
|
||||||
|
0, rusqlite::types::Type::Text, Box::new(e)
|
||||||
|
))?
|
||||||
|
};
|
||||||
|
|
||||||
let ui_config_json_str: String = row.get("ui_config_json")?;
|
let ui_config_json_str: String = row.get("ui_config_json")?;
|
||||||
let ui_config_json: serde_json::Value = serde_json::from_str(&ui_config_json_str)
|
let ui_config_json: serde_json::Value = if ui_config_json_str.trim().is_empty() {
|
||||||
.map_err(|e| rusqlite::Error::FromSqlConversionFailure(
|
serde_json::Value::Object(serde_json::Map::new())
|
||||||
0, rusqlite::types::Type::Text, Box::new(e)
|
} else {
|
||||||
))?;
|
serde_json::from_str(&ui_config_json_str)
|
||||||
|
.map_err(|e| rusqlite::Error::FromSqlConversionFailure(
|
||||||
|
0, rusqlite::types::Type::Text, Box::new(e)
|
||||||
|
))?
|
||||||
|
};
|
||||||
|
|
||||||
let execution_config_json: Option<serde_json::Value> = row.get::<_, Option<String>>("execution_config_json")?
|
let execution_config_json: Option<serde_json::Value> = row.get::<_, Option<String>>("execution_config_json")?
|
||||||
.map(|s| serde_json::from_str(&s))
|
.map(|s| {
|
||||||
|
if s.trim().is_empty() {
|
||||||
|
Ok(serde_json::Value::Object(serde_json::Map::new()))
|
||||||
|
} else {
|
||||||
|
serde_json::from_str(&s)
|
||||||
|
}
|
||||||
|
})
|
||||||
.transpose()
|
.transpose()
|
||||||
.map_err(|e| rusqlite::Error::FromSqlConversionFailure(
|
.map_err(|e| rusqlite::Error::FromSqlConversionFailure(
|
||||||
0, rusqlite::types::Type::Text, Box::new(e)
|
0, rusqlite::types::Type::Text, Box::new(e)
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
let input_schema_json: Option<serde_json::Value> = row.get::<_, Option<String>>("input_schema_json")?
|
let input_schema_json: Option<serde_json::Value> = row.get::<_, Option<String>>("input_schema_json")?
|
||||||
.map(|s| serde_json::from_str(&s))
|
.map(|s| {
|
||||||
|
if s.trim().is_empty() {
|
||||||
|
Ok(serde_json::Value::Object(serde_json::Map::new()))
|
||||||
|
} else {
|
||||||
|
serde_json::from_str(&s)
|
||||||
|
}
|
||||||
|
})
|
||||||
.transpose()
|
.transpose()
|
||||||
.map_err(|e| rusqlite::Error::FromSqlConversionFailure(
|
.map_err(|e| rusqlite::Error::FromSqlConversionFailure(
|
||||||
0, rusqlite::types::Type::Text, Box::new(e)
|
0, rusqlite::types::Type::Text, Box::new(e)
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
let output_schema_json: Option<serde_json::Value> = row.get::<_, Option<String>>("output_schema_json")?
|
let output_schema_json: Option<serde_json::Value> = row.get::<_, Option<String>>("output_schema_json")?
|
||||||
.map(|s| serde_json::from_str(&s))
|
.map(|s| {
|
||||||
|
if s.trim().is_empty() {
|
||||||
|
Ok(serde_json::Value::Object(serde_json::Map::new()))
|
||||||
|
} else {
|
||||||
|
serde_json::from_str(&s)
|
||||||
|
}
|
||||||
|
})
|
||||||
.transpose()
|
.transpose()
|
||||||
.map_err(|e| rusqlite::Error::FromSqlConversionFailure(
|
.map_err(|e| rusqlite::Error::FromSqlConversionFailure(
|
||||||
0, rusqlite::types::Type::Text, Box::new(e)
|
0, rusqlite::types::Type::Text, Box::new(e)
|
||||||
|
|
|
||||||
|
|
@ -130,9 +130,177 @@ fn test_json_operations() {
|
||||||
"value": 42,
|
"value": 42,
|
||||||
"active": true
|
"active": true
|
||||||
});
|
});
|
||||||
|
|
||||||
assert!(json_value.is_object());
|
assert!(json_value.is_object());
|
||||||
assert_eq!(json_value["name"], "test");
|
assert_eq!(json_value["name"], "test");
|
||||||
assert_eq!(json_value["value"], 42);
|
assert_eq!(json_value["value"], 42);
|
||||||
assert_eq!(json_value["active"], true);
|
assert_eq!(json_value["active"], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_database_connection() {
|
||||||
|
use crate::infrastructure::database::Database;
|
||||||
|
|
||||||
|
// 测试数据库连接
|
||||||
|
let database = Database::new().expect("应该能够创建数据库连接");
|
||||||
|
|
||||||
|
// 运行迁移
|
||||||
|
database.run_migrations().expect("应该能够运行数据库迁移");
|
||||||
|
|
||||||
|
// 测试查询工作流模板表
|
||||||
|
let conn = database.get_connection();
|
||||||
|
let conn = conn.lock().unwrap();
|
||||||
|
|
||||||
|
// 检查表是否存在
|
||||||
|
let mut stmt = conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='workflow_templates'")
|
||||||
|
.expect("应该能够准备查询语句");
|
||||||
|
|
||||||
|
let table_exists = stmt.exists([]).expect("应该能够检查表是否存在");
|
||||||
|
assert!(table_exists, "workflow_templates 表应该存在");
|
||||||
|
|
||||||
|
// 检查表结构
|
||||||
|
let mut stmt = conn.prepare("PRAGMA table_info(workflow_templates)")
|
||||||
|
.expect("应该能够准备表结构查询");
|
||||||
|
|
||||||
|
let column_count = stmt.query_map([], |row| {
|
||||||
|
Ok(row.get::<_, String>(1)?) // 获取列名
|
||||||
|
}).expect("应该能够查询表结构")
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.expect("应该能够收集列信息")
|
||||||
|
.len();
|
||||||
|
|
||||||
|
assert!(column_count > 0, "workflow_templates 表应该有列");
|
||||||
|
println!("数据库连接测试通过,workflow_templates 表有 {} 列", column_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_workflow_template_repository() {
|
||||||
|
use crate::data::repositories::workflow_template_repository::WorkflowTemplateRepository;
|
||||||
|
use crate::infrastructure::database::Database;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
// 创建数据库和仓库
|
||||||
|
let database = Arc::new(Database::new().expect("应该能够创建数据库"));
|
||||||
|
database.run_migrations().expect("应该能够运行迁移");
|
||||||
|
|
||||||
|
// 首先检查数据库中的原始数据
|
||||||
|
let conn = database.get_connection();
|
||||||
|
let conn = conn.lock().unwrap();
|
||||||
|
|
||||||
|
// 检查表中是否有数据
|
||||||
|
let count: i64 = conn.query_row("SELECT COUNT(*) FROM workflow_templates", [], |row| {
|
||||||
|
row.get(0)
|
||||||
|
}).expect("应该能够查询记录数量");
|
||||||
|
|
||||||
|
println!("数据库中有 {} 条工作流模板记录", count);
|
||||||
|
|
||||||
|
if count > 0 {
|
||||||
|
// 检查第一条记录的所有字段
|
||||||
|
let mut stmt = conn.prepare("SELECT * FROM workflow_templates LIMIT 1")
|
||||||
|
.expect("应该能够准备查询语句");
|
||||||
|
|
||||||
|
let result = stmt.query_row([], |row| {
|
||||||
|
println!("第一条记录的所有字段:");
|
||||||
|
for i in 0..row.as_ref().column_count() {
|
||||||
|
let column_name = row.as_ref().column_name(i).unwrap_or("unknown");
|
||||||
|
match row.get_ref(i) {
|
||||||
|
Ok(value) => {
|
||||||
|
match value {
|
||||||
|
rusqlite::types::ValueRef::Null => println!(" {}: NULL", column_name),
|
||||||
|
rusqlite::types::ValueRef::Integer(v) => println!(" {}: {}", column_name, v),
|
||||||
|
rusqlite::types::ValueRef::Real(v) => println!(" {}: {}", column_name, v),
|
||||||
|
rusqlite::types::ValueRef::Text(v) => {
|
||||||
|
let text = std::str::from_utf8(v).unwrap_or("invalid utf8");
|
||||||
|
if text.len() > 100 {
|
||||||
|
println!(" {}: {} chars, preview: {:?}", column_name, text.len(), &text[..100]);
|
||||||
|
} else {
|
||||||
|
println!(" {}: {:?}", column_name, text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rusqlite::types::ValueRef::Blob(v) => println!(" {}: blob {} bytes", column_name, v.len()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => println!(" {}: ERROR - {}", column_name, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Err(e) = result {
|
||||||
|
println!("查询第一条记录失败: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(conn); // 释放连接
|
||||||
|
|
||||||
|
// 现在测试手动解析第一条记录
|
||||||
|
if count > 0 {
|
||||||
|
let conn = database.get_connection();
|
||||||
|
let conn = conn.lock().unwrap();
|
||||||
|
|
||||||
|
let mut stmt = conn.prepare("SELECT * FROM workflow_templates LIMIT 1")
|
||||||
|
.expect("应该能够准备查询语句");
|
||||||
|
|
||||||
|
let result = stmt.query_row([], |row| {
|
||||||
|
println!("开始手动解析记录...");
|
||||||
|
|
||||||
|
// 逐个字段解析,找出问题所在
|
||||||
|
let id: Option<i64> = Some(row.get("id")?);
|
||||||
|
println!("✓ ID: {:?}", id);
|
||||||
|
|
||||||
|
let name: String = row.get("name")?;
|
||||||
|
println!("✓ Name: {}", name);
|
||||||
|
|
||||||
|
let base_name: String = row.get("base_name")?;
|
||||||
|
println!("✓ Base name: {}", base_name);
|
||||||
|
|
||||||
|
let version: String = row.get("version")?;
|
||||||
|
println!("✓ Version: {}", version);
|
||||||
|
|
||||||
|
let type_str: String = row.get("type")?;
|
||||||
|
println!("✓ Type: {}", type_str);
|
||||||
|
|
||||||
|
let description: Option<String> = row.get("description")?;
|
||||||
|
println!("✓ Description: {:?}", description);
|
||||||
|
|
||||||
|
// 测试JSON字段解析
|
||||||
|
let comfyui_workflow_json_str: String = row.get("comfyui_workflow_json")?;
|
||||||
|
println!("✓ ComfyUI JSON string length: {}", comfyui_workflow_json_str.len());
|
||||||
|
|
||||||
|
// 尝试解析JSON
|
||||||
|
match serde_json::from_str::<serde_json::Value>(&comfyui_workflow_json_str) {
|
||||||
|
Ok(json) => println!("✓ ComfyUI JSON parsed successfully"),
|
||||||
|
Err(e) => println!("✗ ComfyUI JSON parse error: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
let ui_config_json_str: String = row.get("ui_config_json")?;
|
||||||
|
println!("✓ UI Config JSON string length: {}", ui_config_json_str.len());
|
||||||
|
|
||||||
|
match serde_json::from_str::<serde_json::Value>(&ui_config_json_str) {
|
||||||
|
Ok(json) => println!("✓ UI Config JSON parsed successfully"),
|
||||||
|
Err(e) => println!("✗ UI Config JSON parse error: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Err(e) = result {
|
||||||
|
println!("手动解析失败: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
let repo = WorkflowTemplateRepository::new(database);
|
||||||
|
|
||||||
|
// 测试查询所有模板(应该返回空列表而不是错误)
|
||||||
|
match repo.find_all(None) {
|
||||||
|
Ok(templates) => {
|
||||||
|
println!("成功查询工作流模板,数量: {}", templates.len());
|
||||||
|
// 空列表是正常的,因为还没有插入数据
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
panic!("查询工作流模板失败: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue