verifier/
voyager.rs

1use camino::Utf8PathBuf;
2use scarb_metadata::{Metadata, PackageId};
3use serde::Deserialize;
4use std::{collections::HashMap, path::PathBuf};
5use thiserror::Error;
6
7pub type ContractMap = HashMap<String, Voyager>;
8
9#[allow(dead_code)]
10#[derive(Clone, Debug, Deserialize)]
11pub struct Voyager {
12    pub path: PathBuf,
13    pub address: Option<String>,
14}
15
16#[derive(Debug, Error)]
17pub enum Error {
18    #[error(transparent)]
19    Deserialization(#[from] serde_json::Error),
20}
21
22// Use this instead of metadata.runtime_manifest, because of:
23// https://docs.rs/scarb-metadata/latest/scarb_metadata/struct.Metadata.html#compatibility
24// > With very old Scarb versions (<0.5.0), this field may end up being
25// > empty path upon deserializing from scarb metadata call. In this
26// >  case, fall back to WorkspaceMetadata.manifest field value.
27// but I've actually got this in scarb 0.5.1, so...
28#[must_use]
29pub fn manifest_path(metadata: &Metadata) -> &Utf8PathBuf {
30    if metadata.runtime_manifest == Utf8PathBuf::new() {
31        &metadata.workspace.manifest_path
32    } else {
33        &metadata.runtime_manifest
34    }
35}
36
37/// # Errors
38///
39/// Will return `Err` if `tool.voyager` section can't be deserialized.
40pub fn tool_section(metadata: &Metadata) -> Result<HashMap<PackageId, ContractMap>, Error> {
41    let mut voyager: HashMap<PackageId, ContractMap> = HashMap::new();
42    for package in &metadata.packages {
43        if !metadata.workspace.members.contains(&package.id) {
44            continue;
45        }
46
47        if let Some(tool) = package.tool_metadata("voyager") {
48            let contracts =
49                serde_json::from_value::<ContractMap>(tool.clone()).map_err(Error::from)?;
50            voyager.insert(package.id.clone(), contracts);
51        }
52    }
53    Ok(voyager)
54}
55
56#[cfg(test)]
57#[allow(clippy::unwrap_used)]
58mod tests {
59    use super::*;
60    use camino::Utf8PathBuf;
61    use std::path::PathBuf;
62
63    #[test]
64    fn test_manifest_path_with_runtime_manifest() {
65        // Create a minimal test without complex scarb_metadata structs
66        let runtime_manifest = Utf8PathBuf::from("/test/project/Scarb.toml");
67        let workspace_manifest = Utf8PathBuf::from("/test/project/Scarb.toml");
68
69        // Test the logic: if runtime_manifest is not empty, use it
70        if runtime_manifest != Utf8PathBuf::new() {
71            assert_eq!(
72                runtime_manifest,
73                Utf8PathBuf::from("/test/project/Scarb.toml")
74            );
75        } else {
76            assert_eq!(
77                workspace_manifest,
78                Utf8PathBuf::from("/test/project/Scarb.toml")
79            );
80        }
81    }
82
83    #[test]
84    fn test_manifest_path_fallback_to_workspace() {
85        // Test the fallback logic
86        let runtime_manifest = Utf8PathBuf::new(); // Empty path
87        let workspace_manifest = Utf8PathBuf::from("/test/project/Scarb.toml");
88
89        let result = if runtime_manifest == Utf8PathBuf::new() {
90            &workspace_manifest
91        } else {
92            &runtime_manifest
93        };
94
95        assert_eq!(result, &Utf8PathBuf::from("/test/project/Scarb.toml"));
96    }
97
98    #[test]
99    fn test_voyager_clone() {
100        let voyager = Voyager {
101            path: PathBuf::from("/test/path"),
102            address: Some("0x123".to_string()),
103        };
104        let cloned = voyager.clone();
105        assert_eq!(voyager.path, cloned.path);
106        assert_eq!(voyager.address, cloned.address);
107    }
108
109    #[test]
110    fn test_voyager_debug() {
111        let voyager = Voyager {
112            path: PathBuf::from("/test/path"),
113            address: Some("0x123".to_string()),
114        };
115        let debug_str = format!("{voyager:?}");
116        assert!(debug_str.contains("/test/path"));
117        assert!(debug_str.contains("0x123"));
118    }
119
120    #[test]
121    fn test_error_display() {
122        let json_error = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
123        let error = Error::Deserialization(json_error);
124        let error_string = format!("{error}");
125        assert!(error_string.contains("expected value"));
126    }
127
128    #[test]
129    fn test_contract_map_functionality() {
130        let mut contract_map = ContractMap::new();
131        contract_map.insert(
132            "contract1".to_string(),
133            Voyager {
134                path: PathBuf::from("/test/contract1.cairo"),
135                address: Some("0x123".to_string()),
136            },
137        );
138        contract_map.insert(
139            "contract2".to_string(),
140            Voyager {
141                path: PathBuf::from("/test/contract2.cairo"),
142                address: None,
143            },
144        );
145
146        assert_eq!(contract_map.len(), 2);
147        assert!(contract_map.contains_key("contract1"));
148        assert!(contract_map.contains_key("contract2"));
149
150        let contract1 = contract_map.get("contract1").unwrap();
151        assert_eq!(contract1.address, Some("0x123".to_string()));
152
153        let contract2 = contract_map.get("contract2").unwrap();
154        assert_eq!(contract2.address, None);
155    }
156}