Skip to main content

check_deprule/
lib.rs

1use anyhow::Context;
2use std::{env, path::PathBuf, process::ExitCode};
3
4pub mod dependency_graph;
5pub mod dependency_rule;
6pub mod metadata;
7
8#[derive(Debug, Clone)]
9pub enum ReturnStatus {
10    NoViolation,
11    Violation,
12}
13impl ReturnStatus {
14    pub fn to_return_code(&self) -> ExitCode {
15        match self {
16            ReturnStatus::NoViolation => ExitCode::SUCCESS,
17            ReturnStatus::Violation => ExitCode::FAILURE,
18        }
19    }
20}
21
22pub struct HandlerConfig {
23    pub graph_build_configs: dependency_graph::DependencyGraphBuildConfigs,
24    pub metadata_configs: metadata::CollectMetadataConfig,
25    pub tree_config: dependency_graph::tree::TreePrintConfig,
26    pub rules_path: Option<PathBuf>,
27}
28
29pub fn handler(config: HandlerConfig) -> anyhow::Result<ReturnStatus> {
30    tracing::info!("collecting cargo metadata");
31    let metadata = metadata::collect_metadata(config.metadata_configs.clone())?;
32
33    tracing::info!("building dependency graph");
34    let graph = dependency_graph::build_dependency_graph(&metadata, config.graph_build_configs)?;
35
36    let rules_path = match config.rules_path {
37        Some(path) => path,
38        None => {
39            let manifest_path = match config.metadata_configs.manifest_path {
40                Some(path) => PathBuf::from(path),
41                None => env::current_dir()?.join("Cargo.toml"),
42            };
43            let rules_dir = manifest_path
44                .parent()
45                .ok_or_else(|| anyhow::anyhow!("manifest path has no parent directory"))?;
46            rules_dir.join("dependency_rules.toml")
47        }
48    };
49    tracing::info!(path = ?rules_path, "loading dependency rules");
50    let rules = dependency_rule::DependencyRules::from_file(&rules_path).with_context(|| {
51        format!(
52            "failed to load dependency rules from '{}'",
53            rules_path.display()
54        )
55    })?;
56
57    tracing::info!("checking violations");
58    let report = dependency_graph::violation::check_violations(&graph, &rules);
59
60    tracing::info!("printing dependency tree");
61    dependency_graph::tree::print(
62        &mut std::io::stdout(),
63        &graph,
64        &metadata,
65        &report,
66        config.tree_config,
67    )?;
68
69    if report.has_violations() {
70        Ok(ReturnStatus::Violation)
71    } else {
72        Ok(ReturnStatus::NoViolation)
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::{
80        dependency_graph::{DependencyGraphBuildConfigs, tree::TreePrintConfig},
81        metadata::CollectMetadataConfig,
82    };
83    use anyhow::Result;
84
85    fn handler_config(manifest_path: &str) -> HandlerConfig {
86        HandlerConfig {
87            graph_build_configs: DependencyGraphBuildConfigs::default(),
88            metadata_configs: CollectMetadataConfig {
89                manifest_path: Some(manifest_path.to_string()),
90                ..CollectMetadataConfig::default()
91            },
92            tree_config: TreePrintConfig::default(),
93            rules_path: None,
94        }
95    }
96
97    #[test]
98    #[ignore]
99    fn test_main() -> Result<()> {
100        let config = handler_config("tests/demo_crates/clean-arch/Cargo.toml");
101        let _ = handler(config)?;
102        Ok(())
103    }
104
105    #[test]
106    fn test_handler_success() -> Result<()> {
107        let config = handler_config("tests/demo_crates/clean-arch/Cargo.toml");
108        let result = handler(config)?;
109        assert_eq!(result.to_return_code(), ExitCode::SUCCESS);
110        Ok(())
111    }
112
113    #[test]
114    fn test_handler_failure() -> Result<()> {
115        let config = handler_config("tests/demo_crates/tangled-clean-arch/Cargo.toml");
116        let result = handler(config)?;
117        assert_eq!(result.to_return_code(), ExitCode::FAILURE);
118        Ok(())
119    }
120}