use serde_json::{json, Value}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CandidateFailureDiagnosticKind { RequestBodyBuild, RequestConversion, BodyRules, HeaderRules, UrlBuild, TransportAuth, EnvelopeBuild, } impl CandidateFailureDiagnosticKind { pub const fn as_str(self) -> &'static str { match self { Self::RequestBodyBuild => "request_body_build", Self::RequestConversion => "request_conversion", Self::BodyRules => "body_rules", Self::HeaderRules => "header_rules", Self::UrlBuild => "url_build", Self::TransportAuth => "transport_auth", Self::EnvelopeBuild => "envelope_build", } } } #[derive(Debug, Clone)] pub struct CandidateFailureDiagnostic { kind: CandidateFailureDiagnosticKind, path: String, message: String, source: Option, client_api_format: Option, provider_api_format: Option, safe_to_show: bool, details: Option, } impl CandidateFailureDiagnostic { pub fn new( kind: CandidateFailureDiagnosticKind, path: impl Into, message: impl Into, ) -> Self { Self { kind, path: path.into(), message: message.into(), source: None, client_api_format: None, provider_api_format: None, safe_to_show: true, details: None, } } pub fn source(mut self, source: impl Into) -> Self { self.source = Some(source.into()); self } pub fn details(mut self, details: Value) -> Self { self.details = Some(details); self } pub fn formats( mut self, client_api_format: impl Into, provider_api_format: impl Into, ) -> Self { self.client_api_format = Some(client_api_format.into()); self.provider_api_format = Some(provider_api_format.into()); self } pub fn has_specific_path(&self) -> bool { let path = self.path.trim(); !path.is_empty() && path != "$" } pub fn to_extra_data(&self) -> Value { let diagnostic = self.to_value(); let mut extra_data = json!({ "failure_diagnostic": diagnostic, }); // Compatibility for current usage UI and already persisted trace readers. if let Some(object) = extra_data.as_object_mut() { match self.kind { CandidateFailureDiagnosticKind::RequestBodyBuild => { object.insert( "request_body_build_error".to_string(), json!({ "path": self.path, "message": self.message, "client_api_format": self.client_api_format, "provider_api_format": self.provider_api_format, }), ); } CandidateFailureDiagnosticKind::RequestConversion => { object.insert( "request_conversion_error".to_string(), json!({ "path": self.path, "message": self.message, "client_api_format": self.client_api_format, "provider_api_format": self.provider_api_format, }), ); } _ => {} } } extra_data } pub fn upstream_url_missing( client_api_format: impl Into, provider_api_format: impl Into, source: impl Into, ) -> Self { Self::new( CandidateFailureDiagnosticKind::UrlBuild, "$.endpoint", "无法构建上游请求地址;请检查 base_url、custom_path、API 格式和模型映射", ) .formats(client_api_format, provider_api_format) .source(source) } pub fn header_rules_apply_failed( client_api_format: impl Into, provider_api_format: impl Into, source: impl Into, ) -> Self { Self::new( CandidateFailureDiagnosticKind::HeaderRules, "$.endpoint.header_rules", "Header 规则应用失败;请检查规则格式、条件配置,或是否试图覆盖受保护认证头", ) .formats(client_api_format, provider_api_format) .source(source) } pub fn body_rules_apply_failed( client_api_format: impl Into, provider_api_format: impl Into, source: impl Into, ) -> Self { Self::new( CandidateFailureDiagnosticKind::BodyRules, "$.endpoint.body_rules", "Body 规则应用失败;请检查规则格式、条件配置,或规则输出是否仍是当前上游支持的请求体", ) .formats(client_api_format, provider_api_format) .source(source) } pub fn body_rules_unsupported_for_binary_upload( client_api_format: impl Into, provider_api_format: impl Into, source: impl Into, ) -> Self { Self::new( CandidateFailureDiagnosticKind::BodyRules, "$.endpoint.body_rules", "二进制上传暂不支持本地应用 Body 规则;请移除该 Endpoint 的 Body 规则或改用 JSON 请求体", ) .formats(client_api_format, provider_api_format) .source(source) } pub fn provider_request_body_missing( client_api_format: impl Into, provider_api_format: impl Into, source: impl Into, ) -> Self { Self::new( CandidateFailureDiagnosticKind::RequestBodyBuild, "$", "无法构建上游请求体;请检查请求体是否为支持的 JSON object,以及该任务类型必需字段是否存在且取值受支持", ) .formats(client_api_format, provider_api_format) .source(source) } pub fn request_conversion_failed( client_api_format: impl Into, provider_api_format: impl Into, source: impl Into, message: impl Into, ) -> Self { Self::new( CandidateFailureDiagnosticKind::RequestConversion, "$", message, ) .formats(client_api_format, provider_api_format) .source(source) } pub fn envelope_build_failed( client_api_format: impl Into, provider_api_format: impl Into, source: impl Into, ) -> Self { Self::new( CandidateFailureDiagnosticKind::EnvelopeBuild, "$", "无法构建上游请求封装;请检查该 Provider 的认证配置、模型映射、Endpoint Body 规则和当前请求体是否兼容", ) .formats(client_api_format, provider_api_format) .source(source) } fn to_value(&self) -> Value { json!({ "kind": self.kind.as_str(), "path": self.path, "message": self.message, "source": self.source, "client_api_format": self.client_api_format, "provider_api_format": self.provider_api_format, "safe_to_show": self.safe_to_show, "details": self.details, "stage": "request", "source_format": self.client_api_format, "target_format": self.provider_api_format, }) } }