use regex_lite::Regex;
use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PlaceholderType {
Lid,
CbId,
}
impl PlaceholderType {
pub fn as_str(&self) -> &'static str {
match self {
PlaceholderType::Lid => "lid",
PlaceholderType::CbId => "cb_id",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Placeholder {
pub ty: Option<PlaceholderType>,
pub start: usize,
pub end: usize,
}
pub const TOKEN: &str = "__BRAZESYNC__";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResolutionError {
UnresolvedLid {
start: usize,
anchor: Option<String>,
},
UnresolvedCbId { start: usize, name: Option<String> },
UnknownContext { start: usize },
RetiredNamespace { token: String },
}
fn lid_prefix_re() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| Regex::new(r#"\|\s*lid:\s*['"]$"#).expect("lid prefix regex is valid"))
}
fn cb_id_prefix_re() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| Regex::new(r#"\|\s*id:\s*['"]$"#).expect("cb_id prefix regex is valid"))
}
pub fn infer_type(prefix: &str) -> Option<PlaceholderType> {
if lid_prefix_re().is_match(prefix) {
return Some(PlaceholderType::Lid);
}
if cb_id_prefix_re().is_match(prefix) {
return Some(PlaceholderType::CbId);
}
None
}
pub fn extract_placeholders(body: &str) -> Vec<Placeholder> {
let mut out = Vec::new();
let mut i = 0;
while let Some(rel) = body[i..].find(TOKEN) {
let start = i + rel;
let end = start + TOKEN.len();
let ty = infer_type(&body[..start]);
out.push(Placeholder { ty, start, end });
i = end;
}
out
}
pub fn has_placeholders(body: &str) -> bool {
body.contains(TOKEN)
}
fn retired_envelope_re() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
Regex::new(r"__BRAZE?SYNC\.[A-Za-z0-9_]+\.[A-Za-z0-9_]+__")
.expect("retired envelope regex is valid")
})
}
fn typo_token_re() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| Regex::new(r"__BRAZE?SYNC[A-Z]*__").expect("typo token regex is valid"))
}
pub fn find_suspicious_placeholders(body: &str) -> Vec<String> {
let mut out = Vec::new();
for m in retired_envelope_re().find_iter(body) {
out.push(m.as_str().to_string());
}
for m in typo_token_re().find_iter(body) {
let s = m.as_str();
if s == TOKEN {
continue;
}
if out.iter().any(|o: &String| o.starts_with(s)) {
continue;
}
out.push(s.to_string());
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extracts_anonymous_lid_token() {
let body = "x | lid: '__BRAZESYNC__' y";
let ps = extract_placeholders(body);
assert_eq!(ps.len(), 1);
assert_eq!(ps[0].ty, Some(PlaceholderType::Lid));
}
#[test]
fn extracts_anonymous_cb_id_token() {
let body = "x | id: '__BRAZESYNC__' y";
let ps = extract_placeholders(body);
assert_eq!(ps.len(), 1);
assert_eq!(ps[0].ty, Some(PlaceholderType::CbId));
}
#[test]
fn double_quoted_context_recognized() {
let body = r#"| lid: "__BRAZESYNC__""#;
let ps = extract_placeholders(body);
assert_eq!(ps[0].ty, Some(PlaceholderType::Lid));
}
#[test]
fn token_without_filter_context_has_none_type() {
let body = "bare __BRAZESYNC__ token";
let ps = extract_placeholders(body);
assert_eq!(ps.len(), 1);
assert!(ps[0].ty.is_none());
}
#[test]
fn token_outside_quotes_has_none_type() {
let body = "| lid: __BRAZESYNC__";
let ps = extract_placeholders(body);
assert!(ps[0].ty.is_none());
}
#[test]
fn multiple_tokens_in_order() {
let body = "a | lid: '__BRAZESYNC__' b | id: '__BRAZESYNC__' c";
let ps = extract_placeholders(body);
assert_eq!(ps.len(), 2);
assert_eq!(ps[0].ty, Some(PlaceholderType::Lid));
assert_eq!(ps[1].ty, Some(PlaceholderType::CbId));
assert!(ps[0].start < ps[1].start);
}
#[test]
fn retired_envelope_is_suspicious() {
let body = "stuff __BRAZESYNC.lid.foo__ stuff";
let warns = find_suspicious_placeholders(body);
assert_eq!(warns, vec!["__BRAZESYNC.lid.foo__".to_string()]);
}
#[test]
fn retired_custom_namespace_is_suspicious() {
let body = "x __BRAZESYNC.custom.foo__ y";
let warns = find_suspicious_placeholders(body);
assert_eq!(warns, vec!["__BRAZESYNC.custom.foo__".to_string()]);
}
#[test]
fn typo_token_is_suspicious() {
let body = "x __BRAZSYNC__ y";
let warns = find_suspicious_placeholders(body);
assert!(warns.iter().any(|w| w.contains("BRAZSYNC")));
}
#[test]
fn strict_token_is_not_suspicious() {
let body = "x __BRAZESYNC__ y";
assert!(find_suspicious_placeholders(body).is_empty());
}
}