use async_trait::async_trait; pub enum CsamResult { Clean, Match, } #[async_trait] pub trait CsamScanner: Send + Sync { async fn check(&self, image_data: &[u8]) -> Result; } pub struct PhotoDnaScanner { client: reqwest::Client, endpoint: String, api_key: String, } impl PhotoDnaScanner { pub fn new(endpoint: String, api_key: String) -> Self { Self { client: reqwest::Client::new(), endpoint, api_key, } } } #[async_trait] impl CsamScanner for PhotoDnaScanner { async fn check(&self, image_data: &[u8]) -> Result { let url = format!( "{}/contentmoderator/moderate/v1.0/ProcessImage/Match", self.endpoint ); let resp = self .client .post(&url) .header("Ocp-Apim-Subscription-Key", &self.api_key) .header("Content-Type", "application/octet-stream") .body(image_data.to_vec()) .send() .await .map_err(|e| format!("photodna request failed: {e}"))?; if !resp.status().is_success() { let status = resp.status(); return Err(format!("photodna returned {status}")); } let body: serde_json::Value = resp .json() .await .map_err(|e| format!("photodna response parse failed: {e}"))?; if body["IsMatch"].as_bool() == Some(true) { Ok(CsamResult::Match) } else { Ok(CsamResult::Clean) } } }