lib

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

package_boundary.rs (10911B)


      1 use std::collections::BTreeSet;
      2 
      3 use radroots_nostr_connect::{
      4     BunkerUri, Client, ClientUri, Error, Method, Permission, Request, Response, Server,
      5     client::Transport,
      6 };
      7 
      8 const MANIFEST: &str = include_str!("../Cargo.toml");
      9 const README: &str = include_str!("../README.md");
     10 const EXAMPLE: &str = include_str!("../examples/prepare_request.rs");
     11 const PUBLIC_API: &str =
     12     include_str!("../../../contracts/api_baselines/radroots_nostr_connect.txt");
     13 const CLIENT: &str = include_str!("../src/client.rs");
     14 const METHOD: &str = include_str!("../src/method.rs");
     15 const PERMISSION: &str = include_str!("../src/permission.rs");
     16 const ROOT: &str = include_str!("../src/lib.rs");
     17 const SERVER: &str = include_str!("../src/server.rs");
     18 const URI: &str = include_str!("../src/uri.rs");
     19 
     20 #[test]
     21 fn manifest_has_final_identity_feature_vocabulary_and_radroots_dependencies() {
     22     for required in [
     23         "name = \"radroots_nostr_connect\"",
     24         "version = \"0.1.0-alpha\"",
     25         "publish = [\"crates-io\"]",
     26         "[lib]\nname = \"radroots_nostr_connect\"",
     27         "default = [\"serde\"]",
     28         "radroots_event = { workspace = true, default-features = false }",
     29         "radroots_identity = { workspace = true, default-features = false }",
     30         "radroots_nostr = { workspace = true, default-features = false }",
     31         "radroots_protocol = { workspace = true, default-features = false }",
     32     ] {
     33         assert!(
     34             MANIFEST.contains(required),
     35             "manifest is missing `{required}`"
     36         );
     37     }
     38 
     39     assert_eq!(
     40         table_keys(MANIFEST, "[features]"),
     41         BTreeSet::from(["default", "serde"])
     42     );
     43     assert_eq!(
     44         table_keys(MANIFEST, "[dependencies]")
     45             .into_iter()
     46             .filter(|dependency| dependency.starts_with("radroots_"))
     47             .collect::<BTreeSet<_>>(),
     48         BTreeSet::from([
     49             "radroots_event",
     50             "radroots_identity",
     51             "radroots_nostr",
     52             "radroots_protocol",
     53         ])
     54     );
     55     assert_eq!(
     56         table_keys(MANIFEST, "[dependencies]"),
     57         BTreeSet::from([
     58             "nostr",
     59             "radroots_event",
     60             "radroots_identity",
     61             "radroots_nostr",
     62             "radroots_protocol",
     63             "serde",
     64             "serde_json",
     65             "thiserror",
     66             "url",
     67         ])
     68     );
     69     assert_eq!(
     70         table_keys(MANIFEST, "[dev-dependencies]"),
     71         BTreeSet::from(["tokio"])
     72     );
     73     for forbidden in [
     74         "keyring",
     75         "nostr-sdk",
     76         "reqwest",
     77         "sqlx",
     78         "radroots_secrets",
     79         "radroots_storage",
     80         "radroots_transport_nostr",
     81     ] {
     82         assert!(
     83             !table_keys(MANIFEST, "[dependencies]").contains(forbidden),
     84             "protocol package acquired forbidden production dependency `{forbidden}`"
     85         );
     86     }
     87 }
     88 
     89 #[test]
     90 fn crate_root_contains_the_approved_module_skeleton() {
     91     assert_eq!(
     92         declarations(ROOT, "pub mod "),
     93         BTreeSet::from([
     94             "client",
     95             "error",
     96             "message",
     97             "method",
     98             "permission",
     99             "server",
    100             "uri",
    101         ])
    102     );
    103     assert_eq!(
    104         ROOT.lines()
    105             .map(str::trim)
    106             .filter(|line| line.starts_with("pub use "))
    107             .collect::<BTreeSet<_>>(),
    108         BTreeSet::from([
    109             "pub use client::Client;",
    110             "pub use error::RadrootsNostrConnectError as Error;",
    111             "pub use message::{Request, Response};",
    112             "pub use method::Method;",
    113             "pub use permission::Permission;",
    114             "pub use server::Server;",
    115             "pub use uri::{BunkerUri, ClientUri};",
    116         ])
    117     );
    118 
    119     assert!(SERVER.starts_with("//! Relay- and persistence-independent NIP-46 server state."));
    120     assert!(ROOT.contains("pub use server::Server;"));
    121     assert!(!ROOT.contains("pub mod prelude"));
    122 }
    123 
    124 #[test]
    125 fn approved_root_exports_and_transport_trait_compile() {
    126     fn assert_value<T: Send + Sync>() {}
    127     fn assert_error<T: core::error::Error + Send + Sync>() {}
    128     fn accept_transport(_: &mut dyn Transport) {}
    129 
    130     assert_value::<BunkerUri>();
    131     assert_value::<Client>();
    132     assert_value::<ClientUri>();
    133     assert_value::<Method>();
    134     assert_value::<Permission>();
    135     assert_value::<Request>();
    136     assert_value::<Response>();
    137     assert_value::<Server>();
    138     assert_error::<Error>();
    139     let _ = accept_transport;
    140 
    141     let public_traits = CLIENT
    142         .lines()
    143         .map(str::trim)
    144         .filter(|line| line.starts_with("pub trait "))
    145         .collect::<BTreeSet<_>>();
    146     assert_eq!(
    147         public_traits,
    148         BTreeSet::from(["pub trait Transport: Send {"]),
    149         "only the final host transport SPI may remain"
    150     );
    151     for forbidden in [
    152         "Relay", "Runtime", "Session", "Storage", "Secret", "Approval",
    153     ] {
    154         assert!(
    155             public_traits
    156                 .iter()
    157                 .all(|trait_line| !trait_line.contains(forbidden)),
    158             "protocol package exposes forbidden owner trait containing `{forbidden}`"
    159         );
    160     }
    161 }
    162 
    163 #[test]
    164 fn uri_method_and_permission_types_use_canonical_owners_and_names() {
    165     for root_export in [
    166         "pub use method::Method;",
    167         "pub use permission::Permission;",
    168         "pub use uri::{BunkerUri, ClientUri};",
    169     ] {
    170         assert!(ROOT.contains(root_export), "missing `{root_export}`");
    171     }
    172     for forbidden in [
    173         "pub enum RadrootsNostrConnectMethod",
    174         "pub struct RadrootsNostrConnectPermission",
    175         "pub struct RadrootsNostrConnectPermissions",
    176     ] {
    177         assert!(!METHOD.contains(forbidden));
    178         assert!(!PERMISSION.contains(forbidden));
    179     }
    180     for forbidden in [
    181         "pub struct RadrootsNostrConnectBunkerUri",
    182         "pub struct RadrootsNostrConnectClientUri",
    183         "pub enum RadrootsNostrConnectUri",
    184         "use nostr::{PublicKey",
    185     ] {
    186         assert!(!URI.contains(forbidden), "URI source retains `{forbidden}`");
    187     }
    188     assert!(URI.contains("use radroots_identity::PublicKey;"));
    189     assert!(URI.contains("radroots_nostr::key::parse_public_key"));
    190 }
    191 
    192 #[test]
    193 fn client_root_and_transport_use_package_owned_state_machine_types() {
    194     assert!(ROOT.contains("pub use client::Client;"));
    195     for required in [
    196         "pub struct Client {",
    197         "pub struct ClientEvent(Event);",
    198         "pub struct Target {",
    199         "pub trait Transport: Send {",
    200         "pub enum CancellationPhase {",
    201     ] {
    202         assert!(CLIENT.contains(required), "client is missing `{required}`");
    203     }
    204     for forbidden in [
    205         "pub struct Client {\n    pub ",
    206         "pub struct ClientEvent(pub ",
    207         "pub struct Target {\n    pub ",
    208     ] {
    209         assert!(
    210             !CLIENT.contains(forbidden),
    211             "client exposes representation through `{forbidden}`"
    212         );
    213     }
    214 }
    215 
    216 #[test]
    217 fn separate_repository_compatibility_surface_is_removed() {
    218     assert!(MANIFEST.contains("publish = [\"crates-io\"]"));
    219     for item in [
    220         "pub type RadrootsNostrConnectClientTransportFuture",
    221         "pub struct RadrootsNostrConnectClientTarget",
    222         "pub struct RadrootsNostrConnectClientRequest",
    223         "pub enum RadrootsNostrConnectClientProgress",
    224         "pub enum RadrootsNostrConnectClientEventOutcome",
    225         "pub trait RadrootsNostrConnectClientTransport",
    226         "pub fn build_request_event",
    227         "pub fn parse_response_event",
    228         "pub async fn execute_request_with_transport",
    229     ] {
    230         assert!(
    231             !CLIENT.contains(item),
    232             "compatibility item `{item}` must be absent"
    233         );
    234     }
    235     for legacy in [
    236         "RadrootsNostrConnectClient",
    237         "radroots_nostr_connect::prelude",
    238     ] {
    239         assert!(
    240             !PUBLIC_API.contains(legacy) && !ROOT.contains(legacy),
    241             "compatibility identity `{legacy}` remains"
    242         );
    243     }
    244 }
    245 
    246 #[test]
    247 fn protocol_transport_boundary_has_no_relay_pool_or_runtime_owner() {
    248     for forbidden in [
    249         "nostr-sdk",
    250         "nostr_sdk",
    251         "tokio::runtime",
    252         "RelayPool",
    253         "Client::start",
    254     ] {
    255         assert!(
    256             !CLIENT.contains(forbidden) && !MANIFEST.contains(forbidden),
    257             "protocol transport retains forbidden owner `{forbidden}`"
    258         );
    259     }
    260     assert!(CLIENT.contains("pub trait Transport: Send"));
    261     assert!(CLIENT.contains("T: Transport + ?Sized"));
    262 }
    263 
    264 #[test]
    265 fn predecessor_signer_package_is_removed() {
    266     let crate_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
    267     assert!(
    268         !crate_root
    269             .parent()
    270             .expect("crates root")
    271             .join("nostr_signer")
    272             .exists()
    273     );
    274 }
    275 
    276 #[test]
    277 fn package_documentation_and_reviewed_api_baseline_cover_the_public_contract() {
    278     for section in [
    279         "## Canonical surface",
    280         "## Features and supported targets",
    281         "## Serialization and compatibility",
    282         "## Security, side effects, and commit points",
    283         "## Intended consumers",
    284         "## Package charter",
    285     ] {
    286         assert!(README.contains(section), "README is missing `{section}`");
    287     }
    288     for required in [
    289         "Client::generate",
    290         "Target::try_new",
    291         "Request::Ping",
    292         "operation.publication()",
    293     ] {
    294         assert!(
    295             EXAMPLE.contains(required),
    296             "example is missing `{required}`"
    297         );
    298     }
    299     for export in [
    300         "pub struct radroots_nostr_connect::Client",
    301         "pub struct radroots_nostr_connect::Server",
    302         "pub enum radroots_nostr_connect::Method",
    303         "pub struct radroots_nostr_connect::Permission",
    304         "pub enum radroots_nostr_connect::Request",
    305         "pub enum radroots_nostr_connect::Response",
    306         "pub struct radroots_nostr_connect::BunkerUri",
    307         "pub struct radroots_nostr_connect::ClientUri",
    308         "pub enum radroots_nostr_connect::Error",
    309     ] {
    310         assert!(
    311             PUBLIC_API.contains(export),
    312             "reviewed API baseline is missing `{export}`"
    313         );
    314     }
    315 }
    316 
    317 fn table_keys<'a>(source: &'a str, header: &str) -> BTreeSet<&'a str> {
    318     let Some((_, tail)) = source.split_once(header) else {
    319         panic!("manifest is missing {header}");
    320     };
    321 
    322     tail.lines()
    323         .skip(1)
    324         .take_while(|line| !line.trim_start().starts_with('['))
    325         .filter_map(|line| {
    326             let line = line.trim();
    327             if line.is_empty() || line.starts_with('#') || line.starts_with('"') {
    328                 return None;
    329             }
    330             line.split_once('=').map(|(key, _)| key.trim())
    331         })
    332         .collect()
    333 }
    334 
    335 fn declarations<'a>(source: &'a str, prefix: &str) -> BTreeSet<&'a str> {
    336     source
    337         .lines()
    338         .map(str::trim)
    339         .filter_map(|line| {
    340             line.strip_prefix(prefix)
    341                 .and_then(|name| name.strip_suffix(';'))
    342         })
    343         .collect()
    344 }