feature-manifest 0.5.0

Document, validate, and render Cargo feature metadata.
Documentation
use serde::Serialize;

use crate::FeatureManifest;
use crate::ValidationReport;
use crate::WorkspaceManifest;
use crate::cli::commands::check::{PackageReport, Summary};
use crate::cli::util::portable_relative_path;

#[derive(Debug, Serialize)]
struct JsonCheckReport {
    schema_version: u32,
    packages: Vec<JsonCheckPackage>,
    summary: JsonCheckSummary,
}

#[derive(Debug, Serialize)]
struct JsonCheckPackage {
    package_name: Option<String>,
    manifest_path: String,
    errors: usize,
    warnings: usize,
    issues: Vec<JsonIssue>,
}

#[derive(Debug, Serialize)]
struct JsonIssue {
    code: String,
    severity: String,
    feature: Option<String>,
    message: String,
}

#[derive(Debug, Serialize)]
struct JsonCheckSummary {
    packages: usize,
    features: usize,
    groups: usize,
    errors: usize,
    warnings: usize,
}

pub fn render(
    workspace: &WorkspaceManifest,
    package_reports: &[PackageReport<'_>],
    summary: &Summary,
) -> Result<String, serde_json::Error> {
    serde_json::to_string_pretty(&report(workspace, package_reports, summary))
}

fn report(
    workspace: &WorkspaceManifest,
    package_reports: &[(&FeatureManifest, ValidationReport)],
    summary: &Summary,
) -> JsonCheckReport {
    let root_directory = workspace
        .root_manifest_path
        .parent()
        .unwrap_or_else(|| std::path::Path::new("."));

    JsonCheckReport {
        schema_version: 1,
        packages: package_reports
            .iter()
            .map(|(package, report)| JsonCheckPackage {
                package_name: package.package_name.clone(),
                manifest_path: portable_relative_path(root_directory, &package.manifest_path),
                errors: report.error_count(),
                warnings: report.warning_count(),
                issues: report
                    .issues
                    .iter()
                    .map(|issue| JsonIssue {
                        code: issue.code.to_owned(),
                        severity: issue.severity.to_string(),
                        feature: issue.feature.clone(),
                        message: issue.message.clone(),
                    })
                    .collect(),
            })
            .collect(),
        summary: JsonCheckSummary {
            packages: summary.packages,
            features: summary.features,
            groups: summary.groups,
            errors: summary.errors,
            warnings: summary.warnings,
        },
    }
}