use serde::{Deserialize, Serialize}; use crate::types::{Claim, ClaimCheck, ClaimStatus, RelatedClaim}; #[derive(Debug, Deserialize)] pub struct QueryResponse { pub assertions: Vec, pub conflict_score: Option, } #[derive(Debug, Deserialize)] pub struct AssertionResponse { pub subject: String, pub predicate: String, pub object: ObjectValue, pub confidence: f32, pub source_class: String, } #[derive(Debug, Deserialize)] #[serde(untagged)] pub enum ObjectValue { Text(String), Number(f64), Boolean(bool), Link(String), Image(String), } impl ToString for ObjectValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ObjectValue::Text(s) => write!(f, "{}", s), ObjectValue::Number(n) => write!(f, "{}", n), ObjectValue::Boolean(b) => write!(f, "{}", b), ObjectValue::Link(s) => write!(f, "{}", s), ObjectValue::Image(s) => write!(f, "[Image: {}]", s), } } } pub struct Client { url: String, http: reqwest::Client, } impl Client { pub fn new(url: String) -> Self { Self { url: url.trim_end_matches('/').to_string(), http: reqwest::Client::new(), } } pub async fn check_claim(&self, claim: &Claim) -> Result { let url = format!("{}/v1/query", self.url); // Query using Skeptic lens to see conflicts let response = self.http.get(&url) .query(&[ ("subject", &claim.subject), ("predicate", &claim.predicate), ("lens", &"Skeptic".to_string()), ]) .send() .await .map_err(|e| format!("Request failed: {}", e))?; if !response.status().is_success() { return Err(format!("API error: {}", response.status())); } let data: QueryResponse = response.json().await .map_err(|e| format!("Parse error: {}", e))?; let status = if data.assertions.is_empty() { ClaimStatus::New } else if let Some(score) = data.conflict_score { if score > 0.5 { ClaimStatus::Contradicts } else { ClaimStatus::Matches } } else { ClaimStatus::Matches }; let related = data.assertions.into_iter().map(|a| { RelatedClaim { claim: Claim { subject: a.subject, predicate: a.predicate, object: a.object.to_string(), confidence: a.confidence, quote: "".to_string(), source: Some(a.source_class), }, relationship: "existing".to_string(), source: "stemedb".to_string(), } }).collect(); Ok(ClaimCheck { claim: claim.clone(), status, related, }) } pub async fn save_claim(&self, claim: &Claim) -> Result<(), String> { let url = format!("{}/v1/assert", self.url); let body = serde_json::json!({ "subject": claim.subject, "predicate": claim.predicate, "object": { "type": "Text", "value": claim.object }, "confidence": claim.confidence, "source_class": "Anecdotal", // Default for Disputed }); let response = self.http.post(&url) .json(&body) .send() .await .map_err(|e| format!("Request failed: {}", e))?; if !response.status().is_success() { return Err(format!("API error: {}", response.status())); } Ok(()) } }