allwright-core 0.0.34

Lightweight allwright engine core with shared client and transport APIs.
Documentation
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SelectorFlavor {
    Css,
    XPath,
}

impl SelectorFlavor {
    fn as_str(self) -> &'static str {
        match self {
            Self::Css => "css",
            Self::XPath => "xpath",
        }
    }
}

fn decode_selector_body(body: &str) -> String {
    let candidate = body.trim();
    if candidate.len() >= 2 && candidate.starts_with('"') && candidate.ends_with('"') {
        if let Ok(decoded) = serde_json::from_str::<String>(candidate) {
            return decoded;
        }
    }
    candidate.to_string()
}

fn parse_selector_for_transport(selector: &str) -> (SelectorFlavor, String) {
    let trimmed = selector.trim();
    let lowered = trimmed.to_ascii_lowercase();
    if lowered.starts_with("xpath=") || lowered.starts_with("xpath:") {
        return (SelectorFlavor::XPath, decode_selector_body(&trimmed[6..]));
    }
    if lowered.starts_with("css=") || lowered.starts_with("css:") {
        return (SelectorFlavor::Css, decode_selector_body(&trimmed[4..]));
    }
    if trimmed.starts_with("//")
        || trimmed.starts_with(".//")
        || trimmed.starts_with("../")
        || trimmed.starts_with('/')
        || trimmed.starts_with('(')
    {
        return (SelectorFlavor::XPath, trimmed.to_string());
    }
    (SelectorFlavor::Css, trimmed.to_string())
}

pub(crate) fn normalize_selector_for_transport(selector: &str) -> String {
    let (flavor, body) = parse_selector_for_transport(selector);
    format!(
        "{}={}",
        flavor.as_str(),
        serde_json::to_string(&body).unwrap_or_else(|_| format!("{body:?}"))
    )
}

pub(crate) fn chain_selector_for_transport(parent: &str, child: &str) -> String {
    let parent_selector = if parent.trim().is_empty() {
        String::new()
    } else {
        normalize_selector_for_transport(parent)
    };
    let child_selector = if child.trim().is_empty() {
        String::new()
    } else {
        normalize_selector_for_transport(child)
    };
    if parent_selector.is_empty() {
        return child_selector;
    }
    if child_selector.is_empty() {
        return parent_selector;
    }
    format!("{parent_selector} {child_selector}")
}