kcode-rust-bins 1.0.0

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

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

use std::fmt;
use std::path::{Path, PathBuf};
use std::sync::Arc;

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

/// Policy-blind immutable byte storage supplied by the integrating server.
pub trait ObjectStore: Send + Sync {
    fn load(&self, object_id: &str) -> ObjectStoreResult<Vec<u8>>;
    fn save(&self, bytes: &[u8]) -> ObjectStoreResult<String>;
}

/// 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,
    object_store: Arc<dyn ObjectStore>,
}

/// Creates a minimal Rust 2024 binary and returns its complete source.
pub fn create(
    rust_bins_root: impl AsRef<Path>,
    name: &str,
    object_store: Arc<dyn ObjectStore>,
) -> 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,
        object_store,
    })
}

/// Opens an existing binary, migrating a valid legacy flat repository when needed.
pub fn open(
    rust_bins_root: impl AsRef<Path>,
    name: &str,
    object_store: Arc<dyn ObjectStore>,
) -> 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,
        object_store,
    })
}

/// 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)
}

/// Attempts to execute bytes from any supplied object ID.
pub fn run(
    object_store: &dyn ObjectStore,
    binary_object_id: &str,
    request: &RunRequest,
) -> Result<RunResult> {
    execution::run(object_store, binary_object_id, request)
}

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, saves the exact executable, and returns its object ID.
    pub fn publish(&self) -> Result<String> {
        let source = validate_source(&self.files, &self.name)?;
        let executable = sandbox::build(&source, &self.name)?;
        self.object_store
            .save(&executable)
            .map_err(|error| Error::object_store("save executable", None, error))
    }
}

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("object_store", &"[PRIVATE]")
            .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,
    )
}