use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct ContractBinding {
pub binding_names: Vec<String>,
}
#[derive(Debug)]
pub struct ContractIndex {
file_bindings: HashMap<String, Vec<String>>,
pub total_bindings: usize,
pub total_files: usize,
}
impl ContractIndex {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn load(project_path: &Path) -> Option<Self> {
let idx_path = Self::find_index_path(project_path)?;
let content = std::fs::read_to_string(&idx_path).ok()?;
let parsed: serde_json::Value = serde_json::from_str(&content).ok()?;
let obj = parsed.as_object()?;
let mut file_bindings = HashMap::with_capacity(obj.len());
let mut total_bindings = 0usize;
for (file, bindings) in obj {
if let Some(arr) = bindings.as_array() {
let names: Vec<String> = arr
.iter()
.filter_map(|v| {
v.as_str().map(|s| s.to_string()).or_else(|| {
v.get("name")
.and_then(|n| n.as_str())
.map(|s| s.to_string())
})
})
.collect();
total_bindings += names.len();
file_bindings.insert(file.clone(), names);
}
}
let total_files = file_bindings.len();
Some(Self {
file_bindings,
total_bindings,
total_files,
})
}
fn find_index_path(project_path: &Path) -> Option<PathBuf> {
let primary = project_path.join(".pmat/binding-index.json");
if primary.exists() {
return Some(primary);
}
let alt = project_path.join("contracts/binding-index.json");
if alt.exists() {
return Some(alt);
}
None
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn has_bindings(&self, file_path: &str) -> bool {
self.file_bindings.contains_key(file_path)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn get_bindings(&self, file_path: &str) -> &[String] {
self.file_bindings
.get(file_path)
.map(|v| v.as_slice())
.unwrap_or(&[])
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn find_gaps<'a>(&self, files: &'a [String]) -> Vec<&'a String> {
files
.iter()
.filter(|f| !self.file_bindings.contains_key(f.as_str()))
.collect()
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn bound_files(&self) -> impl Iterator<Item = (&str, &[String])> {
self.file_bindings
.iter()
.map(|(k, v)| (k.as_str(), v.as_slice()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_load_empty_index() {
let dir = tempdir().unwrap();
let pmat = dir.path().join(".pmat");
std::fs::create_dir(&pmat).unwrap();
std::fs::write(pmat.join("binding-index.json"), "{}").unwrap();
let idx = ContractIndex::load(dir.path()).unwrap();
assert_eq!(idx.total_bindings, 0);
assert_eq!(idx.total_files, 0);
}
#[test]
fn test_load_with_bindings() {
let dir = tempdir().unwrap();
let pmat = dir.path().join(".pmat");
std::fs::create_dir(&pmat).unwrap();
std::fs::write(
pmat.join("binding-index.json"),
r#"{"src/lib.rs": ["validate_input", "parse_config"], "src/util.rs": ["hash"]}"#,
)
.unwrap();
let idx = ContractIndex::load(dir.path()).unwrap();
assert_eq!(idx.total_bindings, 3);
assert_eq!(idx.total_files, 2);
assert!(idx.has_bindings("src/lib.rs"));
assert!(!idx.has_bindings("src/main.rs"));
assert_eq!(idx.get_bindings("src/lib.rs").len(), 2);
}
#[test]
fn test_find_gaps() {
let dir = tempdir().unwrap();
let pmat = dir.path().join(".pmat");
std::fs::create_dir(&pmat).unwrap();
std::fs::write(
pmat.join("binding-index.json"),
r#"{"src/lib.rs": ["validate"]}"#,
)
.unwrap();
let idx = ContractIndex::load(dir.path()).unwrap();
let files = vec![
"src/lib.rs".to_string(),
"src/main.rs".to_string(),
"src/util.rs".to_string(),
];
let gaps = idx.find_gaps(&files);
assert_eq!(gaps.len(), 2);
assert!(gaps.contains(&&"src/main.rs".to_string()));
assert!(gaps.contains(&&"src/util.rs".to_string()));
}
#[test]
fn test_load_nonexistent() {
let dir = tempdir().unwrap();
assert!(ContractIndex::load(dir.path()).is_none());
}
}