lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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 }