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#[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
37pub 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 let runtime_manifest = Utf8PathBuf::from("/test/project/Scarb.toml");
67 let workspace_manifest = Utf8PathBuf::from("/test/project/Scarb.toml");
68
69 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 let runtime_manifest = Utf8PathBuf::new(); 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}