Skip to main content

harn_cli/package/manifest/
connector_module.rs

1//! Which source file a package declares as a provider's connector module.
2//!
3//! The connector ABI pins every runtime export of that module to a root
4//! `harness: Harness`. `HARN-LNT-056` needs to know before it may propose
5//! narrowing one, and `harn fix` writes that proposal to disk — so the answer
6//! has to come from the manifest's declaration rather than from whatever the
7//! file's own contents imply (harn#6186).
8
9use std::path::{Path, PathBuf};
10
11use crate::package::manifest_search::nearest_manifest_or_warn;
12
13/// Whether the nearest `harn.toml` names `harn_file` under any
14/// `[[providers]] connector = { harn = ... }`.
15///
16/// Both sides are canonicalized so a relative manifest entry and an absolute
17/// walk path compare equal; a path that cannot be canonicalized (it may not
18/// exist yet) falls back to itself, which simply fails to match.
19#[must_use]
20pub fn is_declared_connector_module(harn_file: &Path) -> bool {
21    let Some((manifest, dir)) = nearest_manifest_or_warn(harn_file) else {
22        return false;
23    };
24    let target = canonical(harn_file);
25    manifest
26        .providers
27        .iter()
28        .filter_map(|provider| provider.connector.harn.as_deref())
29        .any(|module| canonical(&dir.join(module)) == target)
30}
31
32fn canonical(path: &Path) -> PathBuf {
33    path.canonicalize().unwrap_or_else(|_| path.to_path_buf())
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39    use crate::package::MANIFEST;
40    use std::fs;
41
42    #[test]
43    fn connector_module_declaration_is_read_from_the_manifest() {
44        let tmp = tempfile::TempDir::new().unwrap();
45        let root = tmp.path();
46        fs::write(
47            root.join(MANIFEST),
48            "[package]\nname = \"probe-connector\"\nversion = \"0.1.0\"\n\n[[providers]]\nid = \"probe\"\nconnector = { harn = \"src/lib.harn\" }\n",
49        )
50        .unwrap();
51        fs::create_dir_all(root.join("src")).unwrap();
52        let declared = root.join("src").join("lib.harn");
53        let sibling = root.join("src").join("helpers.harn");
54        fs::write(&declared, "pub fn provider_id() { return \"probe\" }\n").unwrap();
55        fs::write(&sibling, "pub fn helper() { return 1 }\n").unwrap();
56
57        assert!(is_declared_connector_module(&declared));
58        assert!(
59            !is_declared_connector_module(&sibling),
60            "only the module the manifest names is a connector module"
61        );
62    }
63
64    #[test]
65    fn a_file_with_no_manifest_declares_nothing() {
66        let tmp = tempfile::TempDir::new().unwrap();
67        let orphan = tmp.path().join("lib.harn");
68        fs::write(&orphan, "pub fn provider_id() { return \"probe\" }\n").unwrap();
69
70        assert!(
71            !is_declared_connector_module(&orphan),
72            "no manifest means no declaration, so the lint falls back to inference"
73        );
74    }
75}