use regex_lite::Regex;
use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldKind {
ContentBlock,
EmailHtmlBody,
EmailPlainBody,
EmailSubject,
EmailPreheader,
}
impl FieldKind {
pub fn supports_html_anchor(self) -> bool {
matches!(self, FieldKind::ContentBlock | FieldKind::EmailHtmlBody)
}
pub fn supports_plaintext_anchor(self) -> bool {
matches!(self, FieldKind::EmailPlainBody)
}
}
#[derive(Debug, Clone)]
pub struct TemplatizedField {
pub new_body: String,
pub lid_rewrites: usize,
pub cb_id_rewrites: usize,
pub warnings: Vec<String>,
}
pub fn templatize_body(body: &str, field: FieldKind) -> TemplatizedField {
let mut spans: Vec<DetectionSpan> = Vec::new();
let mut warnings: Vec<String> = Vec::new();
let mut lid_rewrites = 0usize;
let mut cb_id_rewrites = 0usize;
for m in lid_match_re().captures_iter(body) {
let whole = m.get(0).expect("group 0 always present");
if matches!(field, FieldKind::EmailSubject | FieldKind::EmailPreheader) {
warnings.push(format!(
"lid detected in subject/preheader at byte {}; resolved \
positionally at apply/diff time (Nth placeholder = Nth remote lid). \
Verify rendered output if the field contains multiple lid values.",
whole.start()
));
}
spans.push(DetectionSpan {
range: whole.range(),
replacement: "| lid: '__BRAZESYNC__'".to_string(),
});
lid_rewrites += 1;
}
for m in cb_id_match_re().captures_iter(body) {
let whole = m.get(0).expect("group 0 always present");
let name = m.get(1).expect("name capture present").as_str();
spans.push(DetectionSpan {
range: whole.range(),
replacement: format!("{{{{content_blocks.${{{name}}} | id: '__BRAZESYNC__'}}}}"),
});
cb_id_rewrites += 1;
}
spans.sort_by_key(|s| s.range.start);
let mut new_body = body.to_string();
for s in spans.into_iter().rev() {
new_body.replace_range(s.range, &s.replacement);
}
TemplatizedField {
new_body,
lid_rewrites,
cb_id_rewrites,
warnings,
}
}
struct DetectionSpan {
range: std::ops::Range<usize>,
replacement: String,
}
fn lid_match_re() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
Regex::new(r#"\|\s*lid:\s*(?:"[a-z0-9]{8,}"|'[a-z0-9]{8,}')"#)
.expect("lid match regex is valid")
})
}
fn cb_id_match_re() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
Regex::new(
r#"\{\{\s*content_blocks\.\$\{\s*([^\s}|]+)\s*\}\s*\|\s*id:\s*(?:"cb[0-9]+"|'cb[0-9]+')\s*\}\}"#,
)
.expect("cb_id match regex is valid")
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn idempotent_on_already_templatized_body() {
let body = "<p>__BRAZESYNC__ kept verbatim</p>";
let r = templatize_body(body, FieldKind::ContentBlock);
assert_eq!(r.new_body, body);
assert_eq!(r.lid_rewrites, 0);
}
#[test]
fn rewrites_html_lid() {
let body = r#"<a href="https://example.com/spring-sale">{{x | lid: 'ai8kexrxcp03'}}</a>"#;
let r = templatize_body(body, FieldKind::ContentBlock);
assert!(r.new_body.contains("| lid: '__BRAZESYNC__'"));
assert_eq!(r.lid_rewrites, 1);
}
#[test]
fn rewrites_cb_id_include() {
let body = "{{content_blocks.${promo_banner} | id: 'cb42'}}";
let r = templatize_body(body, FieldKind::ContentBlock);
assert!(
r.new_body
.contains("{{content_blocks.${promo_banner} | id: '__BRAZESYNC__'}}"),
"got: {}",
r.new_body
);
assert_eq!(r.cb_id_rewrites, 1);
}
#[test]
fn multiple_lids_in_one_field_all_become_anonymous() {
let body = r#"
<a href="https://example.com/cta">{{x | lid: 'ai8kexrxcp03'}}A</a>
<a href="https://example.com/cta">{{x | lid: 'bj9lfsysxq14'}}B</a>"#;
let r = templatize_body(body, FieldKind::ContentBlock);
let n = r.new_body.matches("| lid: '__BRAZESYNC__'").count();
assert_eq!(n, 2);
}
#[test]
fn plaintext_url_lid_rewritten() {
let body = "Click https://example.com/promo {{x | lid: 'ai8kexrxcp03'}} now.";
let r = templatize_body(body, FieldKind::EmailPlainBody);
assert!(r.new_body.contains("| lid: '__BRAZESYNC__'"));
}
#[test]
fn repeated_cb_id_name_independent_replacements() {
let body = "{{content_blocks.${promo} | id: 'cb10'}} ... \
{{content_blocks.${promo} | id: 'cb10'}}";
let r = templatize_body(body, FieldKind::ContentBlock);
let n = r
.new_body
.matches("{{content_blocks.${promo} | id: '__BRAZESYNC__'}}")
.count();
assert_eq!(n, 2);
}
#[test]
fn subject_lid_emits_positional_warning() {
let body = "{{x | lid: 'ai8kexrxcp03'}}";
let r = templatize_body(body, FieldKind::EmailSubject);
assert!(r.new_body.contains("| lid: '__BRAZESYNC__'"));
assert!(!r.warnings.is_empty());
}
}