harn-vm 0.10.153

Async bytecode virtual machine for the Harn programming language
Documentation
/// Simple URI template matching (RFC 6570 Level 1 only).
///
/// Matches a URI against a template like `file:///{path}` and extracts named
/// variables. Returns `None` if the URI doesn't match the template structure.
#[expect(
    clippy::string_slice,
    reason = "t_pos only reaches slice sites on ASCII '{' bytes (char boundaries), u_pos is a \
              boundary because it advances past find() matches or byte runs identical to \
              whole-char template literals, and find() results are boundaries"
)]
pub(super) fn match_uri_template(
    template: &str,
    uri: &str,
) -> Option<std::collections::HashMap<String, String>> {
    let mut vars = std::collections::HashMap::new();
    let mut t_pos = 0;
    let mut u_pos = 0;
    let t_bytes = template.as_bytes();
    let u_bytes = uri.as_bytes();

    while t_pos < t_bytes.len() {
        if t_bytes[t_pos] == b'{' {
            // Find the closing brace
            let close = template[t_pos..].find('}')? + t_pos;
            let var_name = &template[t_pos + 1..close];
            t_pos = close + 1;

            // Capture everything up to the next literal in the template (or end)
            let next_literal = if t_pos < t_bytes.len() {
                // Find how much literal follows
                let lit_start = t_pos;
                let lit_end = template[t_pos..]
                    .find('{')
                    .map(|i| t_pos + i)
                    .unwrap_or(t_bytes.len());
                Some(&template[lit_start..lit_end])
            } else {
                None
            };

            let value_end = match next_literal {
                Some(lit) if !lit.is_empty() => uri[u_pos..].find(lit).map(|i| u_pos + i)?,
                _ => u_bytes.len(),
            };

            vars.insert(var_name.to_string(), uri[u_pos..value_end].to_string());
            u_pos = value_end;
        } else {
            // Literal character must match
            if u_pos >= u_bytes.len() || t_bytes[t_pos] != u_bytes[u_pos] {
                return None;
            }
            t_pos += 1;
            u_pos += 1;
        }
    }

    if u_pos == u_bytes.len() {
        Some(vars)
    } else {
        None
    }
}

#[expect(
    clippy::string_slice,
    reason = "offsets come from find() of the ASCII braces on the sliced string"
)]
pub(super) fn uri_template_variables(template: &str) -> Vec<String> {
    let mut variables = Vec::new();
    let mut cursor = template;
    while let Some(open) = cursor.find('{') {
        cursor = &cursor[open + 1..];
        let Some(close) = cursor.find('}') else {
            break;
        };
        let name = cursor[..close].trim();
        if !name.is_empty() && !variables.iter().any(|variable| variable == name) {
            variables.push(name.to_string());
        }
        cursor = &cursor[close + 1..];
    }
    variables
}

/// Validate the Level 1 variable syntax the server matcher implements. An
/// RFC 6570 operator or malformed expression must fail before publication,
/// rather than being advertised as a template the server cannot resolve.
pub(super) fn validated_uri_template_variables(template: &str) -> Result<Vec<String>, String> {
    let mut variables = Vec::new();
    let mut sample = String::with_capacity(template.len());
    let mut chars = template.chars();
    while let Some(ch) = chars.next() {
        match ch {
            '}' => return Err("unmatched closing brace".into()),
            '{' => {
                let mut name = String::new();
                loop {
                    match chars.next() {
                        Some('}') => break,
                        Some('{') | None => return Err("unclosed or nested expression".into()),
                        Some(ch) => name.push(ch),
                    }
                }
                if name.is_empty()
                    || name.starts_with('.')
                    || name.ends_with('.')
                    || name.contains("..")
                    || !name
                        .chars()
                        .all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '.')
                {
                    return Err(format!("unsupported template variable '{name}'"));
                }
                if variables.contains(&name) {
                    return Err(format!("duplicate template variable '{name}'"));
                }
                variables.push(name);
                sample.push('x');
            }
            _ => sample.push(ch),
        }
    }
    url::Url::parse(&sample).map_err(|error| format!("invalid URI: {error}"))?;
    Ok(variables)
}