weavatrix-git 0.3.0

Fast, bounded, evidence-carrying Git reader with an optional read-only MCP server
Documentation
use mcport::Value;

pub struct Page {
    pub cursor: usize,
    pub limit: usize,
}

pub fn page(arguments: &Value, default_limit: usize, max_limit: usize) -> Result<Page, String> {
    object(arguments)?;
    let cursor = usize_value(arguments, "cursor", 0)?;
    let limit = usize_value(arguments, "limit", default_limit)?;
    if limit == 0 || limit > max_limit {
        return Err(format!("limit must be between 1 and {max_limit}"));
    }
    Ok(Page { cursor, limit })
}

pub fn revision(arguments: &Value, name: &str, default: Option<&str>) -> Result<String, String> {
    object(arguments)?;
    match arguments.get(name) {
        Some(Value::String(value)) if !value.is_empty() && value.len() <= 256 => Ok(value.clone()),
        Some(Value::String(_)) => Err(format!("{name} must contain 1 to 256 bytes")),
        Some(_) => Err(format!("{name} must be a string")),
        None => default
            .map(str::to_owned)
            .ok_or_else(|| format!("{name} is required")),
    }
}

pub fn boolean(arguments: &Value, name: &str, default: bool) -> Result<bool, String> {
    object(arguments)?;
    match arguments.get(name) {
        Some(Value::Bool(value)) => Ok(*value),
        Some(_) => Err(format!("{name} must be a boolean")),
        None => Ok(default),
    }
}

pub fn optional_i64(arguments: &Value, name: &str) -> Result<Option<i64>, String> {
    object(arguments)?;
    match arguments.get(name) {
        Some(value) => value
            .as_i64()
            .map(Some)
            .ok_or_else(|| format!("{name} must be a signed integer")),
        None => Ok(None),
    }
}

pub fn object(arguments: &Value) -> Result<(), String> {
    if matches!(arguments, Value::Object(_)) {
        Ok(())
    } else {
        Err("arguments must be an object".to_owned())
    }
}

fn usize_value(arguments: &Value, name: &str, default: usize) -> Result<usize, String> {
    match arguments.get(name) {
        Some(value) => {
            let value = value
                .as_u64()
                .ok_or_else(|| format!("{name} must be a non-negative integer"))?;
            usize::try_from(value).map_err(|_| format!("{name} is too large"))
        }
        None => Ok(default),
    }
}