use std::fs;
use std::path::Path;
const FILES_TO_SCAN: &[&str] = &[
"src/lib.rs",
"src/bandwidth.rs",
"src/config.rs",
"src/fanout.rs",
"src/ids.rs",
"src/keyframe.rs",
"src/media.rs",
"src/net.rs",
"src/propagate.rs",
"src/rtcp_stats.rs",
"src/rtc.rs",
"src/udp_loop.rs",
"src/client/mod.rs",
"src/client/accessors.rs",
"src/client/construct.rs",
"src/client/fanout.rs",
"src/client/keyframe.rs",
"src/client/layer.rs",
"src/client/stats.rs",
"src/client/tracks.rs",
"src/metrics/mod.rs",
"src/metrics/prom.rs",
"src/metrics/noop.rs",
"src/registry/mod.rs",
"src/registry/drive.rs",
"src/registry/lifecycle.rs",
];
const ALLOWLIST_SUBSTRINGS: &[&str] = &[
"pub fn from_raw(rtc: str0m::Rtc) -> Self {",
];
#[test]
fn no_str0m_in_public_api_surface() {
let mut violations: Vec<String> = Vec::new();
for path in FILES_TO_SCAN {
if !Path::new(path).exists() {
panic!(
"encapsulation_surface test config drift: {path} not found — update FILES_TO_SCAN"
);
}
let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("read {path}: {e}"));
for (idx, raw_line) in contents.lines().enumerate() {
let line = raw_line.trim_start();
let line_no = idx + 1;
let is_pub_signature = line.starts_with("pub fn ")
|| line.starts_with("pub struct ")
|| line.starts_with("pub enum ")
|| line.starts_with("pub type ")
|| line.starts_with("pub const ")
|| line.starts_with("pub static ");
if !is_pub_signature {
continue;
}
if line.starts_with("pub(") {
continue;
}
if !line.contains("str0m::") {
continue;
}
if line.starts_with("pub struct ") && line.contains('(') {
let paren_content = line.split_once('(').map_or("", |(_, r)| r);
if !paren_content.contains("pub str0m::") {
continue;
}
if paren_content.contains("pub(crate) str0m::")
&& !paren_content.contains("pub str0m::")
{
continue;
}
}
if line.starts_with("pub const ") || line.starts_with("pub static ") {
if let Some((_, after_colon)) = line.split_once(':') {
let declared_type = after_colon
.split_once('=')
.map_or(after_colon, |(l, _)| l)
.trim();
if !declared_type.contains("str0m::") {
continue; }
}
}
if ALLOWLIST_SUBSTRINGS
.iter()
.any(|allowed| line.contains(allowed))
{
continue;
}
violations.push(format!("{path}:{line_no}: {}", raw_line.trim()));
}
}
if !violations.is_empty() {
panic!(
"Public API exposes str0m types (encapsulation regression):\n{}\n\n\
If this is intentional, add to the ALLOWLIST_SUBSTRINGS list in this test.",
violations.join("\n")
);
}
}
#[test]
fn raw_module_contains_expected_exports() {
let contents = fs::read_to_string("src/raw.rs").expect("read src/raw.rs");
assert!(
contents.contains("pub use str0m::Rtc as RawRtc"),
"src/raw.rs must re-export str0m::Rtc as RawRtc"
);
assert!(
contents.contains("pub use str0m::RtcConfig as RawRtcConfig"),
"src/raw.rs must re-export str0m::RtcConfig as RawRtcConfig"
);
}