mars-agents 0.4.3

Agent package manager for .agents/ directories
Documentation
use std::path::Path;

use crate::config::SourceSpec;
use crate::error::MarsError;
use crate::types::{SourceId, SourceName, SourceSubpath, SourceUrl};

use super::types::RootedSourceRef;

pub(crate) fn apply_subpath(
    source_name: &SourceName,
    checkout_root: &Path,
    subpath: Option<&SourceSubpath>,
) -> Result<RootedSourceRef, MarsError> {
    let package_root = match subpath {
        Some(subpath) => {
            subpath
                .join_under(checkout_root)
                .map_err(|_| MarsError::SubpathTraversal {
                    source_name: source_name.to_string(),
                    subpath: subpath.to_string(),
                    checkout_root: checkout_root.to_path_buf(),
                })?
        }
        None => checkout_root.to_path_buf(),
    };

    if !package_root.exists() {
        return match subpath {
            Some(subpath) => Err(MarsError::SubpathMissing {
                source_name: source_name.to_string(),
                subpath: subpath.to_string(),
                checkout_root: checkout_root.to_path_buf(),
            }),
            None => Err(MarsError::Source {
                source_name: source_name.to_string(),
                message: format!(
                    "package root does not exist under checkout root `{}`",
                    checkout_root.display()
                ),
            }),
        };
    }

    if !package_root.is_dir() {
        return match subpath {
            Some(subpath) => Err(MarsError::SubpathNotDirectory {
                source_name: source_name.to_string(),
                subpath: subpath.to_string(),
                checkout_root: checkout_root.to_path_buf(),
            }),
            None => Err(MarsError::Source {
                source_name: source_name.to_string(),
                message: format!(
                    "package root is not a directory under checkout root `{}`",
                    checkout_root.display()
                ),
            }),
        };
    }

    let canonical_checkout = dunce::canonicalize(checkout_root).map_err(|e| MarsError::Source {
        source_name: source_name.to_string(),
        message: format!(
            "failed to canonicalize checkout root `{}`: {e}",
            checkout_root.display()
        ),
    })?;
    let canonical_package = dunce::canonicalize(&package_root).map_err(|e| MarsError::Source {
        source_name: source_name.to_string(),
        message: format!(
            "failed to canonicalize package root `{}`: {e}",
            package_root.display()
        ),
    })?;

    if !canonical_package.starts_with(&canonical_checkout) {
        return match subpath {
            Some(subpath) => Err(MarsError::SubpathTraversal {
                source_name: source_name.to_string(),
                subpath: subpath.to_string(),
                checkout_root: checkout_root.to_path_buf(),
            }),
            None => Err(MarsError::Source {
                source_name: source_name.to_string(),
                message: format!(
                    "package root escapes checkout root `{}`",
                    checkout_root.display()
                ),
            }),
        };
    }

    Ok(RootedSourceRef {
        checkout_root: checkout_root.to_path_buf(),
        package_root,
    })
}

pub(crate) fn source_id_for_pending_spec(
    base_root: &Path,
    spec: &SourceSpec,
    subpath: Option<SourceSubpath>,
) -> SourceId {
    match spec {
        SourceSpec::Git(git) => {
            let canonical_url = SourceUrl::from(crate::source::canonical::canonicalize_git_url(
                git.url.as_ref(),
            ));
            SourceId::git_with_subpath(canonical_url, subpath)
        }
        SourceSpec::Path(path) => {
            match SourceId::path_with_subpath(base_root, path, subpath.clone()) {
                Ok(id) => id,
                Err(_) => {
                    let canonical = if path.is_absolute() {
                        path.clone()
                    } else {
                        base_root.join(path)
                    };
                    SourceId::Path { canonical, subpath }
                }
            }
        }
    }
}