covcon 0.3.0

Crate to convert between code coverage formats.
Documentation
//! Types for the loose Cobertura [XML schema](https://github.com/cobertura/cobertura/blob/master/cobertura/src/site/htdocs/xml/coverage-loose.dtd).

use std::path::PathBuf;

use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(rename = "coverage")]
pub struct Coverage {
    #[serde(rename = "@line-rate", skip_serializing_if = "Option::is_none")]
    pub line_rate: Option<f64>,
    #[serde(rename = "@branch-rate", skip_serializing_if = "Option::is_none")]
    pub branch_rate: Option<f64>,
    #[serde(rename = "@lines-covered", skip_serializing_if = "Option::is_none")]
    pub lines_covered: Option<u64>,
    #[serde(rename = "@lines-valid", skip_serializing_if = "Option::is_none")]
    pub lines_valid: Option<u64>,
    #[serde(rename = "@branches-covered", skip_serializing_if = "Option::is_none")]
    pub branches_covered: Option<u64>,
    #[serde(rename = "@branches-valid", skip_serializing_if = "Option::is_none")]
    pub branches_valid: Option<u64>,
    #[serde(rename = "@complexity", skip_serializing_if = "Option::is_none")]
    pub complexity: Option<f64>,
    #[serde(rename = "@version")]
    pub version: String,
    #[serde(rename = "@timestamp")]
    pub timestamp: String,
    pub sources: Option<Sources>,
    pub packages: Packages,
}

impl TryFrom<&str> for Coverage {
    type Error = Box<dyn core::error::Error + Send + Sync + 'static>;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        Ok(quick_xml::de::from_str::<Coverage>(value)?)
    }
}

impl std::fmt::Display for Coverage {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")?;
        writeln!(f, "<!DOCTYPE coverage SYSTEM \"https://github.com/cobertura/cobertura/blob/master/cobertura/src/site/htdocs/xml/coverage-loose.dtd\">")?;
        writeln!(f, "{}", quick_xml::se::to_string(self).unwrap_or_default())
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct Sources {
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub source: Vec<String>,
}

impl std::ops::Deref for Sources {
    type Target = Vec<String>;

    fn deref(&self) -> &Self::Target {
        &self.source
    }
}

impl std::ops::DerefMut for Sources {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.source
    }
}

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Packages {
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub package: Vec<Package>,
}

impl std::ops::Deref for Packages {
    type Target = Vec<Package>;

    fn deref(&self) -> &Self::Target {
        &self.package
    }
}

impl std::ops::DerefMut for Packages {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.package
    }
}

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Package {
    #[serde(rename = "@name")]
    pub name: String,
    #[serde(rename = "@line-rate", skip_serializing_if = "Option::is_none")]
    pub line_rate: Option<f64>,
    #[serde(rename = "@branch-rate", skip_serializing_if = "Option::is_none")]
    pub branch_rate: Option<f64>,
    #[serde(rename = "@complexity", skip_serializing_if = "Option::is_none")]
    pub complexity: Option<f64>,
    pub classes: Classes,
}

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Classes {
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub class: Vec<Class>,
}

impl std::ops::Deref for Classes {
    type Target = Vec<Class>;

    fn deref(&self) -> &Self::Target {
        &self.class
    }
}

impl std::ops::DerefMut for Classes {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.class
    }
}

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Class {
    #[serde(rename = "@name")]
    pub name: String,
    #[serde(rename = "@filename")]
    pub filename: PathBuf,
    #[serde(rename = "@line-rate", skip_serializing_if = "Option::is_none")]
    pub line_rate: Option<f64>,
    #[serde(rename = "@branch-rate", skip_serializing_if = "Option::is_none")]
    pub branch_rate: Option<f64>,
    #[serde(rename = "@complexity", skip_serializing_if = "Option::is_none")]
    pub complexity: Option<f64>,
    pub methods: Methods,
    pub lines: Lines,
}

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Methods {
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub method: Vec<Method>,
}

impl std::ops::Deref for Methods {
    type Target = Vec<Method>;

    fn deref(&self) -> &Self::Target {
        &self.method
    }
}

impl std::ops::DerefMut for Methods {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.method
    }
}

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Method {
    #[serde(rename = "@name")]
    pub name: String,
    #[serde(rename = "@signature")]
    pub signature: String,
    #[serde(rename = "@line-rate", skip_serializing_if = "Option::is_none")]
    pub line_rate: Option<f64>,
    #[serde(rename = "@branch-rate", skip_serializing_if = "Option::is_none")]
    pub branch_rate: Option<f64>,
    #[serde(rename = "@complexity", skip_serializing_if = "Option::is_none")]
    pub complexity: Option<f64>,
    pub lines: Lines,
}

#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct Lines {
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub line: Vec<Line>,
}

impl std::ops::Deref for Lines {
    type Target = Vec<Line>;

    fn deref(&self) -> &Self::Target {
        &self.line
    }
}

impl std::ops::DerefMut for Lines {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.line
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct Line {
    #[serde(rename = "@number")]
    pub number: u64,
    #[serde(rename = "@hits", skip_serializing_if = "Option::is_none")]
    pub hits: Option<u64>,
    #[serde(rename = "@branch", default)]
    pub branch: bool,
    #[serde(
        rename = "@condition-coverage",
        skip_serializing_if = "Option::is_none"
    )]
    pub condition_coverage: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub conditions: Option<Conditions>,
}

#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct Conditions {
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub condition: Vec<Condition>,
}

impl std::ops::Deref for Conditions {
    type Target = Vec<Condition>;

    fn deref(&self) -> &Self::Target {
        &self.condition
    }
}

impl std::ops::DerefMut for Conditions {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.condition
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct Condition {
    #[serde(rename = "@number")]
    pub number: u64,
    #[serde(rename = "@type")]
    pub condition_type: String,
    #[serde(rename = "@coverage")]
    pub coverage: String,
}