kcode-rust-bins 3.0.0

Author, locally publish, and run small Rust binaries
Documentation
//! Minimal managed lifecycle for small object-published Rust binaries.

mod execution;
mod model;
mod protocol;
mod publication;
mod repository;
mod sandbox;

use std::fmt;
use std::path::{Path, PathBuf};
use std::time::Duration;

pub use model::{Error, File, Result, RunRequest, RunResult, RustBinInput, RustBinOutput};
use model::{ValidSource, validate_name, validate_source};
pub use protocol::{read_input, write_output};

/// Default maximum running time for one binary call.
pub const DEFAULT_RUN_TIMEOUT: Duration = Duration::from_secs(2 * 60);

/// One complete editable managed-binary source snapshot.
pub struct Lib {
    /// Every useful UTF-8 source file, canonically sorted after open or write.
    pub files: Vec<File>,
    repository: PathBuf,
    name: String,
    identity: String,
    publications: PathBuf,
}

/// Creates a minimal Rust 2024 binary and returns its complete source.
pub fn create(
    rust_bins_root: impl AsRef<Path>,
    publications_root: impl AsRef<Path>,
    name: &str,
) -> Result<Lib> {
    validate_name(name)?;
    let source = initial_source(name)?;
    let snapshot = repository::create(rust_bins_root.as_ref(), name, &source)?;
    Ok(Lib {
        files: snapshot.files,
        repository: snapshot.repository,
        name: name.to_owned(),
        identity: snapshot.identity,
        publications: publications_root.as_ref().to_path_buf(),
    })
}

/// Opens an existing binary, migrating a valid legacy flat repository when needed.
pub fn open(
    rust_bins_root: impl AsRef<Path>,
    publications_root: impl AsRef<Path>,
    name: &str,
) -> Result<Lib> {
    validate_name(name)?;
    let snapshot = repository::open(rust_bins_root.as_ref(), name)?;
    Ok(Lib {
        files: snapshot.files,
        repository: snapshot.repository,
        name: name.to_owned(),
        identity: snapshot.identity,
        publications: publications_root.as_ref().to_path_buf(),
    })
}

/// Returns the package version and root `Documentation.md`, migrating when needed.
pub fn docs(rust_bins_root: impl AsRef<Path>, name: &str) -> Result<(String, String)> {
    validate_name(name)?;
    repository::docs(rust_bins_root.as_ref(), name)
}

/// Resolves and executes a locally published binary.
///
/// `version_selector` follows the Web-library convention: `v1.2.3` is exact,
/// while unprefixed and abbreviated selectors are Cargo-compatible
/// requirements. `None` uses [`DEFAULT_RUN_TIMEOUT`].
pub fn run(
    publications_root: impl AsRef<Path>,
    name: &str,
    version_selector: &str,
    request: RunRequest,
    timeout: Option<Duration>,
) -> Result<RunResult> {
    validate_name(name)?;
    let publication = publication::resolve(publications_root.as_ref(), name, version_selector)?;
    execution::run(
        &publication.executable,
        publication.version.to_string(),
        request,
        timeout.unwrap_or(DEFAULT_RUN_TIMEOUT),
    )
}

impl Lib {
    /// Atomically commits `files` as the complete source if this snapshot is current.
    pub fn write(&mut self) -> Result<()> {
        let source = validate_source(&self.files, &self.name)?;
        let snapshot = repository::replace(&self.repository, &self.identity, &source)?;
        self.files = snapshot.files;
        self.identity = snapshot.identity;
        Ok(())
    }

    /// Validates and statically builds exactly the current in-memory source.
    pub fn check(&self) -> Result<()> {
        let source = validate_source(&self.files, &self.name)?;
        sandbox::check(&source, &self.name)
    }

    /// Validates once and atomically publishes this exact name and version.
    pub fn publish(&self) -> Result<()> {
        let source = validate_source(&self.files, &self.name)?;
        let executable = sandbox::build(&source, &self.name)?;
        publication::publish(&self.publications, &self.name, &source.version, &executable)
    }
}

impl fmt::Debug for Lib {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("Lib")
            .field("files", &self.files)
            .field("repository", &self.repository)
            .field("name", &self.name)
            .field("identity", &"[PRIVATE]")
            .field("publications", &self.publications)
            .finish()
    }
}

fn initial_source(name: &str) -> Result<ValidSource> {
    validate_source(
        &[
            File {
                path: "Cargo.toml".to_owned(),
                contents: format!(
                    "[package]\nname = \"{name}\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\n"
                ),
            },
            File {
                path: "Documentation.md".to_owned(),
                contents: String::new(),
            },
            File {
                path: "src/main.rs".to_owned(),
                contents: "fn main() {}\n".to_owned(),
            },
        ],
        name,
    )
}