archmap 1.0.2

Static architectural analysis of codebases.
Documentation
use crate::model::AnalysisResult;
use crate::output::OutputFormatter;
use serde::Serialize;
use std::io::Write;
use std::path::{Path, PathBuf};

pub struct JsonOutput {
    project_root: Option<PathBuf>,
}

impl JsonOutput {
    pub fn new(project_root: Option<PathBuf>) -> Self {
        Self { project_root }
    }

    fn relative_path(&self, path: &Path) -> String {
        if let Some(ref root) = self.project_root {
            path.strip_prefix(root)
                .unwrap_or(path)
                .display()
                .to_string()
        } else {
            path.display().to_string()
        }
    }
}

impl Default for JsonOutput {
    fn default() -> Self {
        Self::new(None)
    }
}

#[derive(Serialize)]
struct JsonResult<'a> {
    project_name: &'a str,
    modules: Vec<JsonModule<'a>>,
    issues: Vec<JsonIssue<'a>>,
}

#[derive(Serialize)]
struct JsonModule<'a> {
    path: String,
    name: &'a str,
    lines: usize,
    imports: &'a [String],
    exports: &'a [String],
}

#[derive(Serialize)]
struct JsonIssue<'a> {
    kind: String,
    severity: String,
    message: &'a str,
    locations: Vec<JsonLocation<'a>>,
    suggestion: Option<&'a str>,
}

#[derive(Serialize)]
struct JsonLocation<'a> {
    path: String,
    line: Option<usize>,
    context: Option<&'a str>,
}

impl OutputFormatter for JsonOutput {
    fn format<W: Write>(&self, result: &AnalysisResult, writer: &mut W) -> std::io::Result<()> {
        let json_result = JsonResult {
            project_name: &result.project_name,
            modules: result
                .modules
                .iter()
                .map(|m| JsonModule {
                    path: self.relative_path(&m.path),
                    name: &m.name,
                    lines: m.lines,
                    imports: &m.imports,
                    exports: &m.exports,
                })
                .collect(),
            issues: result
                .issues
                .iter()
                .map(|i| JsonIssue {
                    kind: format!("{:?}", i.kind),
                    severity: i.severity.to_string(),
                    message: &i.message,
                    locations: i
                        .locations
                        .iter()
                        .map(|l| JsonLocation {
                            path: self.relative_path(&l.path),
                            line: l.line,
                            context: l.context.as_deref(),
                        })
                        .collect(),
                    suggestion: i.suggestion.as_deref(),
                })
                .collect(),
        };

        let json = serde_json::to_string_pretty(&json_result)
            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;

        writeln!(writer, "{}", json)
    }
}