fix: resolve list_matching_results deserialization error

- Add custom deserializer for MatchingResultStatus to handle empty strings
- Add serde(default) attributes to TemplateMatchingResultQueryOptions fields
- Update frontend to send undefined instead of empty string for status filter
- Fix 'unknown variant' and 'missing field' errors in template matching results

Resolves issue where selecting 'All Status' filter caused command failures.
This commit is contained in:
imeepos 2025-07-16 21:28:45 +08:00
parent d5ef9851cd
commit d3ab2aa284
2 changed files with 55 additions and 4 deletions

View File

@ -1,4 +1,4 @@
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Serialize, Deserializer};
use chrono::{DateTime, Utc};
/// 模板匹配结果实体模型
@ -28,7 +28,7 @@ pub struct TemplateMatchingResult {
}
/// 匹配结果状态
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Clone, Serialize, PartialEq)]
pub enum MatchingResultStatus {
/// 匹配成功
Success,
@ -40,6 +40,27 @@ pub enum MatchingResultStatus {
Cancelled,
}
/// 自定义反序列化实现,处理空字符串和无效值
impl<'de> Deserialize<'de> for MatchingResultStatus {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.as_str() {
"Success" => Ok(MatchingResultStatus::Success),
"PartialSuccess" => Ok(MatchingResultStatus::PartialSuccess),
"Failed" => Ok(MatchingResultStatus::Failed),
"Cancelled" => Ok(MatchingResultStatus::Cancelled),
"" => {
// 空字符串被视为无效值,返回错误让调用者处理
Err(serde::de::Error::custom("Empty string is not a valid MatchingResultStatus"))
}
_ => Err(serde::de::Error::unknown_variant(&s, &["Success", "PartialSuccess", "Failed", "Cancelled"])),
}
}
}
impl Default for MatchingResultStatus {
fn default() -> Self {
Self::Success
@ -294,16 +315,44 @@ pub struct CreateTemplateMatchingResultRequest {
pub description: Option<String>,
}
/// 自定义反序列化函数,处理空字符串状态
fn deserialize_optional_status<'de, D>(deserializer: D) -> Result<Option<MatchingResultStatus>, D::Error>
where
D: Deserializer<'de>,
{
let opt = Option::<String>::deserialize(deserializer)?;
match opt {
Some(s) if s.is_empty() => Ok(None), // 空字符串转换为 None
Some(s) => match s.as_str() {
"Success" => Ok(Some(MatchingResultStatus::Success)),
"PartialSuccess" => Ok(Some(MatchingResultStatus::PartialSuccess)),
"Failed" => Ok(Some(MatchingResultStatus::Failed)),
"Cancelled" => Ok(Some(MatchingResultStatus::Cancelled)),
_ => Err(serde::de::Error::unknown_variant(&s, &["Success", "PartialSuccess", "Failed", "Cancelled"])),
},
None => Ok(None),
}
}
/// 模板匹配结果查询选项
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct TemplateMatchingResultQueryOptions {
#[serde(default)]
pub project_id: Option<String>,
#[serde(default)]
pub template_id: Option<String>,
#[serde(default)]
pub binding_id: Option<String>,
#[serde(default, deserialize_with = "deserialize_optional_status")]
pub status: Option<MatchingResultStatus>,
#[serde(default)]
pub limit: Option<u32>,
#[serde(default)]
pub offset: Option<u32>,
#[serde(default)]
pub search_keyword: Option<String>,
#[serde(default)]
pub sort_by: Option<String>, // "created_at", "success_rate", "result_name"
#[serde(default)]
pub sort_order: Option<String>, // "asc", "desc"
}

View File

@ -69,7 +69,7 @@ export const TemplateMatchingResultManager: React.FC<TemplateMatchingResultManag
template_id: templateId,
binding_id: bindingId,
status: filters.status,
search_keyword: filters.searchKeyword,
search_keyword: filters.searchKeyword || undefined,
sort_by: filters.sortBy,
sort_order: filters.sortOrder,
limit: pagination.pageSize,
@ -206,7 +206,9 @@ export const TemplateMatchingResultManager: React.FC<TemplateMatchingResultManag
<CustomSelect
options={statusOptions}
value={filters.status || ''}
onChange={(value) => handleFilterChange({ status: value as MatchingResultStatus })}
onChange={(value) => handleFilterChange({
status: value === '' ? undefined : value as MatchingResultStatus
})}
placeholder="选择状态"
/>
</div>