package_boundary.rs (5350B)
1 use std::{collections::BTreeSet, fs, path::Path}; 2 3 use radroots_identity::{ 4 AccountId, Error, IdentityId, Profile, PublicIdentity, PublicKey, Username, 5 account::{Record, Status}, 6 }; 7 8 const MANIFEST: &str = include_str!("../Cargo.toml"); 9 const ROOT: &str = include_str!("../src/lib.rs"); 10 11 #[test] 12 fn manifest_has_exact_features_and_dependencies() { 13 assert_eq!( 14 table_keys(MANIFEST, "[features]"), 15 BTreeSet::from(["default", "serde", "std"]) 16 ); 17 assert_eq!( 18 table_keys(MANIFEST, "[dependencies]"), 19 BTreeSet::from(["k256", "serde", "thiserror"]) 20 ); 21 assert_eq!( 22 table_keys(MANIFEST, "[dev-dependencies]"), 23 BTreeSet::from(["serde_json"]) 24 ); 25 assert!(MANIFEST.contains("default = [\"std\", \"serde\"]")); 26 assert!(MANIFEST.contains("std = [\"thiserror/std\"]")); 27 assert!(MANIFEST.contains("serde = [\"dep:serde\"]")); 28 assert!(MANIFEST.contains( 29 "k256 = { version = \"0.13\", default-features = false, features = [\"arithmetic\"] }" 30 )); 31 assert!(MANIFEST.contains("serde = { workspace = true, optional = true }")); 32 assert!(MANIFEST.contains("thiserror = { version = \"2\", default-features = false }")); 33 } 34 35 #[test] 36 fn crate_root_matches_the_curated_module_and_export_contract() { 37 assert!(ROOT.contains("#![cfg_attr(not(feature = \"std\"), no_std)]")); 38 assert_eq!( 39 root_declarations("pub mod "), 40 BTreeSet::from(["account", "key", "profile", "username"]) 41 ); 42 assert_eq!(root_declarations("mod "), BTreeSet::from(["error"])); 43 assert_eq!( 44 ROOT.lines() 45 .map(str::trim) 46 .filter(|line| line.starts_with("pub use ")) 47 .collect::<BTreeSet<_>>(), 48 BTreeSet::from([ 49 "pub use account::AccountId;", 50 "pub use error::Error;", 51 "pub use key::{IdentityId, PublicKey};", 52 "pub use profile::{Profile, PublicIdentity};", 53 "pub use username::Username;", 54 ]) 55 ); 56 } 57 58 #[test] 59 fn intended_public_paths_and_traits_compile() { 60 fn assert_public_value<T: Clone + core::fmt::Debug + Eq + Send + Sync>() {} 61 62 assert_public_value::<AccountId>(); 63 assert_public_value::<IdentityId>(); 64 assert_public_value::<Profile>(); 65 assert_public_value::<PublicIdentity>(); 66 assert_public_value::<PublicKey>(); 67 assert_public_value::<Record>(); 68 assert_public_value::<Status>(); 69 assert_public_value::<Username>(); 70 let _ = core::mem::size_of::<Error>(); 71 } 72 73 #[test] 74 fn production_sources_have_no_forbidden_owners_or_public_traits() { 75 let source_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); 76 let mut sources = Vec::new(); 77 collect_rust_sources(&source_root, &mut sources); 78 assert!(!sources.is_empty()); 79 80 for path in sources { 81 let source = fs::read_to_string(&path).expect("read identity source"); 82 let production = source.split("\n#[cfg(test)]").next().unwrap_or(&source); 83 for line in production.lines() { 84 let trimmed = line.trim_start(); 85 if trimmed.starts_with("//") { 86 continue; 87 } 88 assert!( 89 !trimmed.starts_with("pub trait ") && !trimmed.starts_with("pub unsafe trait "), 90 "identity must not publish traits: {}: {trimmed}", 91 path.display() 92 ); 93 for forbidden in [ 94 "RadrootsIdentity", 95 "RadrootsSecret", 96 "SecretKey", 97 "PrivateKey", 98 "secret_key", 99 "Nsec", 100 "nsec::", 101 "nip49", 102 "Nip49", 103 "nostr::", 104 "std::fs", 105 "std::path", 106 "PathBuf", 107 "Sqlite", 108 "keyring", 109 ] { 110 assert!( 111 !line.contains(forbidden), 112 "identity production source must not contain {forbidden}: {}: {trimmed}", 113 path.display() 114 ); 115 } 116 } 117 } 118 } 119 120 fn table_keys<'a>(manifest: &'a str, heading: &str) -> BTreeSet<&'a str> { 121 let table = manifest 122 .split_once(heading) 123 .unwrap_or_else(|| panic!("missing manifest table {heading}")) 124 .1; 125 table 126 .lines() 127 .skip(1) 128 .take_while(|line| !line.trim_start().starts_with('[')) 129 .filter_map(|line| { 130 let line = line.trim(); 131 (!line.is_empty() && !line.starts_with('#')) 132 .then(|| line.split_once('=').map(|(key, _)| key.trim())) 133 .flatten() 134 }) 135 .collect() 136 } 137 138 fn root_declarations(prefix: &str) -> BTreeSet<&str> { 139 ROOT.lines() 140 .map(str::trim) 141 .filter_map(|line| { 142 line.strip_prefix(prefix) 143 .and_then(|name| name.strip_suffix(';')) 144 }) 145 .collect() 146 } 147 148 fn collect_rust_sources(directory: &Path, paths: &mut Vec<std::path::PathBuf>) { 149 for entry in fs::read_dir(directory).expect("read identity source directory") { 150 let path = entry.expect("source directory entry").path(); 151 if path.is_dir() { 152 collect_rust_sources(&path, paths); 153 } else if path.extension().and_then(|value| value.to_str()) == Some("rs") { 154 paths.push(path); 155 } 156 } 157 }