use serde_json::Value;
pub const ORIGIN_SUBSTITUTION_TOKENS: [&str; 4] = [
"{{mock_origin}}",
"{{mock_alt_origin}}",
"{{mock_sub_origin}}",
"{{mock_foreign_origin}}",
];
pub fn known_tokens_display() -> String {
ORIGIN_SUBSTITUTION_TOKENS.join(", ")
}
pub fn contains_origin_token(value: &str) -> bool {
ORIGIN_SUBSTITUTION_TOKENS.iter().any(|token| value.contains(token))
}
pub fn entry_has_origin_token(entry: &Value) -> bool {
let body_has_token = entry
.get("body_inline")
.and_then(Value::as_str)
.is_some_and(contains_origin_token);
let header_has_token = entry
.get("headers")
.and_then(Value::as_object)
.is_some_and(|headers| headers.values().any(|v| v.as_str().is_some_and(contains_origin_token)));
body_has_token || header_has_token
}
pub fn find_unknown_origin_token(value: &str) -> Option<String> {
const PREFIX: &str = "{{mock_";
const SUFFIX: &str = "}}";
let mut rest = value;
while let Some(start) = rest.find(PREFIX) {
let candidate_and_beyond = &rest[start..];
let Some(end) = candidate_and_beyond.find(SUFFIX) else {
return Some(candidate_and_beyond.to_string());
};
let candidate = &candidate_and_beyond[..end + SUFFIX.len()];
if !ORIGIN_SUBSTITUTION_TOKENS.contains(&candidate) {
return Some(candidate.to_string());
}
rest = &candidate_and_beyond[end + SUFFIX.len()..];
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn contains_origin_token_true_for_each_known_token() {
for token in ORIGIN_SUBSTITUTION_TOKENS {
let body = format!("<a href=\"{token}/page2\">link</a>");
assert!(
contains_origin_token(&body),
"expected {token} to be detected in: {body}"
);
}
}
#[test]
fn contains_origin_token_false_for_plain_text() {
assert!(!contains_origin_token("<html><body>no tokens here</body></html>"));
}
#[test]
fn entry_has_origin_token_true_for_body_inline_token() {
let entry = serde_json::json!({
"path": "/",
"status_code": 200,
"body_inline": "<a href=\"{{mock_alt_origin}}/page2\">cross-host</a>"
});
assert!(entry_has_origin_token(&entry));
}
#[test]
fn entry_has_origin_token_true_for_header_token() {
let entry = serde_json::json!({
"path": "/",
"status_code": 302,
"headers": {"Location": "{{mock_sub_origin}}/final"},
"body_inline": ""
});
assert!(entry_has_origin_token(&entry));
}
#[test]
fn entry_has_origin_token_false_when_absent() {
let entry = serde_json::json!({"path": "/data.json", "status_code": 200, "body_inline": "{}"});
assert!(!entry_has_origin_token(&entry));
}
#[test]
fn find_unknown_origin_token_none_for_known_tokens() {
let body = "{{mock_origin}} and {{mock_alt_origin}} and {{mock_sub_origin}} and {{mock_foreign_origin}}";
assert_eq!(find_unknown_origin_token(body), None);
}
#[test]
fn find_unknown_origin_token_detects_typo() {
let body = "see {{mock_alt_orgin}} for details";
assert_eq!(find_unknown_origin_token(body), Some("{{mock_alt_orgin}}".to_string()));
}
#[test]
fn find_unknown_origin_token_detects_unterminated_placeholder() {
let body = "see {{mock_alt_origin for details";
assert_eq!(
find_unknown_origin_token(body),
Some("{{mock_alt_origin for details".to_string())
);
}
#[test]
fn find_unknown_origin_token_ignores_unrelated_double_brace_text() {
assert_eq!(find_unknown_origin_token("digest is {{digest}}"), None);
}
}