indieweb 0.9.3

A collection of utilities for working with the IndieWeb.
Documentation
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BatchActionResult {
    pub index: usize,
    pub success: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub location: Option<url::Url>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
}

impl BatchActionResult {
    pub fn success(index: usize, location: Option<url::Url>) -> Self {
        Self {
            index,
            success: true,
            location,
            error: None,
        }
    }

    pub fn failure(index: usize, error: String) -> Self {
        Self {
            index,
            success: false,
            location: None,
            error: Some(error),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BatchActionResponse {
    pub results: Vec<BatchActionResult>,
}

impl BatchActionResponse {
    pub fn new(results: Vec<BatchActionResult>) -> Self {
        Self { results }
    }

    pub fn is_all_success(&self) -> bool {
        self.results.iter().all(|r| r.success)
    }

    pub fn failed_count(&self) -> usize {
        self.results.iter().filter(|r| !r.success).count()
    }

    pub fn success_count(&self) -> usize {
        self.results.iter().filter(|r| r.success).count()
    }
}

impl IntoIterator for BatchActionResponse {
    type Item = BatchActionResult;
    type IntoIter = std::vec::IntoIter<BatchActionResult>;

    fn into_iter(self) -> Self::IntoIter {
        self.results.into_iter()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn batch_result_success() {
        let result =
            BatchActionResult::success(0, Some("https://example.com/post/1".parse().unwrap()));
        assert!(result.success);
        assert_eq!(result.index, 0);
        assert!(result.location.is_some());
        assert!(result.error.is_none());
    }

    #[test]
    fn batch_result_failure() {
        let result = BatchActionResult::failure(1, "Something went wrong".to_string());
        assert!(!result.success);
        assert_eq!(result.index, 1);
        assert!(result.location.is_none());
        assert!(result.error.is_some());
    }

    #[test]
    fn batch_response_all_success() {
        let response = BatchActionResponse::new(vec![
            BatchActionResult::success(0, None),
            BatchActionResult::success(1, None),
        ]);
        assert!(response.is_all_success());
        assert_eq!(response.success_count(), 2);
        assert_eq!(response.failed_count(), 0);
    }

    #[test]
    fn batch_response_partial_success() {
        let response = BatchActionResponse::new(vec![
            BatchActionResult::success(0, None),
            BatchActionResult::failure(1, "Failed".to_string()),
            BatchActionResult::success(2, None),
        ]);
        assert!(!response.is_all_success());
        assert_eq!(response.success_count(), 2);
        assert_eq!(response.failed_count(), 1);
    }

    #[test]
    fn batch_response_serialization() {
        let response = BatchActionResponse::new(vec![
            BatchActionResult::success(0, Some("https://example.com/post/1".parse().unwrap())),
            BatchActionResult::failure(1, "Invalid request".to_string()),
        ]);
        let json = serde_json::to_string(&response).unwrap();
        assert!(json.contains("\"index\":0"));
        assert!(json.contains("\"success\":true"));
        assert!(json.contains("\"index\":1"));
        assert!(json.contains("\"success\":false"));
    }

    #[test]
    fn batch_response_deserialization() {
        let json = r#"{"results":[{"index":0,"success":true,"location":"https://example.com"},{"index":1,"success":false,"error":"Failed"}]}"#;
        let response: BatchActionResponse = serde_json::from_str(json).unwrap();
        assert_eq!(response.results.len(), 2);
        assert!(response.results[0].success);
        assert!(!response.results[1].success);
    }
}