Something went wrong. Try again.
A lexicon-driven AppView for ATProto.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]#[serde(rename_all = "lowercase")]pub enum ProxyMode { Disabled, Open, Allowlist, Blocklist,}
#[derive(Debug, Clone, Serialize, Deserialize)]pub struct ProxyConfig { pub mode: ProxyMode, pub nsids: Vec<String>,}
impl Default for ProxyConfig { fn default() -> Self { Self { mode: ProxyMode::Open, nsids: vec![], } }}
impl ProxyConfig { pub fn allows(&self, nsid: &str) -> bool { match self.mode { ProxyMode::Disabled => false, ProxyMode::Open => true, ProxyMode::Allowlist => self.nsids.iter().any(|pattern| nsid_matches(pattern, nsid)), ProxyMode::Blocklist => !self.nsids.iter().any(|pattern| nsid_matches(pattern, nsid)), } }}
fn nsid_matches(pattern: &str, nsid: &str) -> bool { if let Some(prefix) = pattern.strip_suffix(".*") { nsid.starts_with(prefix) && nsid.len() > prefix.len() && nsid.as_bytes()[prefix.len()] == b'.' } else { pattern == nsid }}
pub fn validate_nsid_pattern(pattern: &str) -> Result<(), String> { if pattern.is_empty() { return Err("NSID pattern must not be empty".into()); }
let (base, is_wildcard) = if let Some(prefix) = pattern.strip_suffix(".*") { (prefix, true) } else { (pattern, false) };
let segments: Vec<&str> = base.split('.').collect(); if segments.len() < 2 { return Err(format!( "NSID pattern must have at least two segments: {pattern}" )); }
for segment in &segments { if segment.is_empty() { return Err(format!("NSID pattern has empty segment: {pattern}")); } if !segment .chars() .all(|c| c.is_ascii_alphanumeric() || c == '-') { return Err(format!( "NSID segment contains invalid characters: {pattern}" )); } }
if !is_wildcard && segments.len() < 3 { return Err(format!( "Exact NSID must have at least three segments: {pattern}" )); }
Ok(())}
#[cfg(test)]mod tests { use super::*;
#[test] fn default_is_open_with_empty_nsids() { let config = ProxyConfig::default(); assert_eq!(config.mode, ProxyMode::Open); assert!(config.nsids.is_empty()); }
#[test] fn disabled_blocks_everything() { let config = ProxyConfig { mode: ProxyMode::Disabled, nsids: vec![], }; assert!(!config.allows("com.example.feed.getHot")); assert!(!config.allows("anything.at.all")); }
#[test] fn open_allows_everything() { let config = ProxyConfig { mode: ProxyMode::Open, nsids: vec![], }; assert!(config.allows("com.example.feed.getHot")); assert!(config.allows("anything.at.all")); }
#[test] fn allowlist_exact_match() { let config = ProxyConfig { mode: ProxyMode::Allowlist, nsids: vec!["com.example.feed.getHot".into()], }; assert!(config.allows("com.example.feed.getHot")); assert!(!config.allows("com.example.feed.getCold")); }
#[test] fn allowlist_wildcard() { let config = ProxyConfig { mode: ProxyMode::Allowlist, nsids: vec!["com.example.*".into()], }; assert!(config.allows("com.example.feed.getHot")); assert!(config.allows("com.example.anything")); assert!(!config.allows("com.other.feed.getHot")); }
#[test] fn blocklist_exact_match() { let config = ProxyConfig { mode: ProxyMode::Blocklist, nsids: vec!["com.example.feed.getHot".into()], }; assert!(!config.allows("com.example.feed.getHot")); assert!(config.allows("com.example.feed.getCold")); }
#[test] fn blocklist_wildcard() { let config = ProxyConfig { mode: ProxyMode::Blocklist, nsids: vec!["com.example.*".into()], }; assert!(!config.allows("com.example.feed.getHot")); assert!(config.allows("com.other.feed.getHot")); }
#[test] fn validate_valid_nsids() { assert!(validate_nsid_pattern("com.example.feed.getHot").is_ok()); assert!(validate_nsid_pattern("com.example.*").is_ok()); assert!(validate_nsid_pattern("games.gamesgamesgamesgames.*").is_ok()); assert!(validate_nsid_pattern("a.b.c").is_ok()); }
#[test] fn validate_invalid_nsids() { assert!(validate_nsid_pattern("").is_err()); assert!(validate_nsid_pattern("*").is_err()); assert!(validate_nsid_pattern("com").is_err()); assert!(validate_nsid_pattern("com.example.*.foo").is_err()); assert!(validate_nsid_pattern("com..example").is_err()); assert!(validate_nsid_pattern(".com.example").is_err()); assert!(validate_nsid_pattern("com.example.").is_err()); }
#[test] fn roundtrip_json() { let config = ProxyConfig { mode: ProxyMode::Allowlist, nsids: vec!["com.example.*".into()], }; let json = serde_json::to_string(&config).unwrap(); let parsed: ProxyConfig = serde_json::from_str(&json).unwrap(); assert_eq!(parsed.mode, ProxyMode::Allowlist); assert_eq!(parsed.nsids, vec!["com.example.*"]); }}