mcp-proxy-args 0.2.4

Argument loading and file rewriting utilities for mcp-proxy convert
Documentation
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};

use sha2::{Digest, Sha256};
use tempfile::NamedTempFile;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum CacheError {
    #[error("file prefix must contain only ASCII letters, digits, '.', '_' or '-'")]
    InvalidPrefix,
    #[error("failed to create cache directory {path}: {source}")]
    CreateDirectory {
        path: PathBuf,
        #[source]
        source: std::io::Error,
    },
    #[error("failed to set permissions on {path}: {source}")]
    SetPermissions {
        path: PathBuf,
        #[source]
        source: std::io::Error,
    },
    #[error("cache directory {path} is not a real directory")]
    InvalidDirectoryType { path: PathBuf },
    #[error("existing cache target {path} is not a regular file")]
    InvalidExistingType { path: PathBuf },
    #[error("failed to create temporary cache file in {path}: {source}")]
    CreateTemporary {
        path: PathBuf,
        #[source]
        source: std::io::Error,
    },
    #[error("failed to write temporary cache file for {path}: {source}")]
    WriteTemporary {
        path: PathBuf,
        #[source]
        source: std::io::Error,
    },
    #[error("failed to persist cache file {path}: {source}")]
    Persist {
        path: PathBuf,
        #[source]
        source: std::io::Error,
    },
    #[error("existing content-addressed file {path} does not match its expected content")]
    ExistingContentMismatch { path: PathBuf },
    #[error("failed to verify existing cache file {path}: {source}")]
    VerifyExisting {
        path: PathBuf,
        #[source]
        source: std::io::Error,
    },
    #[error("failed to sync cache directory {path}: {source}")]
    SyncDirectory {
        path: PathBuf,
        #[source]
        source: std::io::Error,
    },
}

fn validate_prefix(prefix: &str) -> Result<(), CacheError> {
    if prefix.is_empty()
        || !prefix
            .bytes()
            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-'))
    {
        return Err(CacheError::InvalidPrefix);
    }
    Ok(())
}

fn ensure_cache_dir(path: &Path) -> Result<(), CacheError> {
    fs::create_dir_all(path).map_err(|source| CacheError::CreateDirectory {
        path: path.to_path_buf(),
        source,
    })?;
    let metadata = fs::symlink_metadata(path).map_err(|source| CacheError::CreateDirectory {
        path: path.to_path_buf(),
        source,
    })?;
    if !metadata.file_type().is_dir() {
        return Err(CacheError::InvalidDirectoryType {
            path: path.to_path_buf(),
        });
    }

    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        fs::set_permissions(path, fs::Permissions::from_mode(0o700)).map_err(|source| {
            CacheError::SetPermissions {
                path: path.to_path_buf(),
                source,
            }
        })?;
    }

    Ok(())
}

fn verify_existing(path: &Path, expected: &[u8]) -> Result<(), CacheError> {
    let metadata = fs::symlink_metadata(path).map_err(|source| CacheError::VerifyExisting {
        path: path.to_path_buf(),
        source,
    })?;
    if !metadata.file_type().is_file() {
        return Err(CacheError::InvalidExistingType {
            path: path.to_path_buf(),
        });
    }
    let existing = fs::read(path).map_err(|source| CacheError::VerifyExisting {
        path: path.to_path_buf(),
        source,
    })?;
    if existing != expected {
        return Err(CacheError::ExistingContentMismatch {
            path: path.to_path_buf(),
        });
    }

    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        fs::set_permissions(path, fs::Permissions::from_mode(0o600)).map_err(|source| {
            CacheError::SetPermissions {
                path: path.to_path_buf(),
                source,
            }
        })?;
    }
    Ok(())
}

pub(crate) fn write_content_addressed(
    cache_dir: &Path,
    prefix: &str,
    kind: &'static str,
    bytes: &[u8],
) -> Result<(PathBuf, bool), CacheError> {
    validate_prefix(prefix)?;
    ensure_cache_dir(cache_dir)?;

    let digest = Sha256::digest(bytes);
    let hash = format!("{digest:x}");
    let target = cache_dir.join(format!("{prefix}-{kind}-{hash}.json"));

    if target.exists() {
        verify_existing(&target, bytes)?;
        return Ok((target, false));
    }

    let mut temporary =
        NamedTempFile::new_in(cache_dir).map_err(|source| CacheError::CreateTemporary {
            path: cache_dir.to_path_buf(),
            source,
        })?;

    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        temporary
            .as_file()
            .set_permissions(fs::Permissions::from_mode(0o600))
            .map_err(|source| CacheError::SetPermissions {
                path: target.clone(),
                source,
            })?;
    }

    temporary
        .as_file_mut()
        .write_all(bytes)
        .and_then(|()| temporary.as_file_mut().sync_all())
        .map_err(|source| CacheError::WriteTemporary {
            path: target.clone(),
            source,
        })?;

    match temporary.persist_noclobber(&target) {
        Ok(_) => {
            #[cfg(unix)]
            fs::File::open(cache_dir)
                .and_then(|directory| directory.sync_all())
                .map_err(|source| CacheError::SyncDirectory {
                    path: cache_dir.to_path_buf(),
                    source,
                })?;
            Ok((target, true))
        }
        Err(error) if error.error.kind() == std::io::ErrorKind::AlreadyExists => {
            verify_existing(&target, bytes)?;
            Ok((target, false))
        }
        Err(error) => Err(CacheError::Persist {
            path: target,
            source: error.error,
        }),
    }
}