Skip to main content

kcode_rust_bins/
lib.rs

1//! Minimal managed lifecycle for small object-published Rust binaries.
2
3mod execution;
4mod model;
5mod protocol;
6mod repository;
7mod sandbox;
8
9use std::fmt;
10use std::path::{Path, PathBuf};
11use std::sync::Arc;
12
13pub use model::{
14    Error, File, ObjectStoreError, ObjectStoreResult, Result, RunRequest, RunResult, RustBinInput,
15    RustBinOutput,
16};
17use model::{ValidSource, validate_name, validate_source};
18pub use protocol::{read_input, write_output};
19
20/// Policy-blind immutable byte storage supplied by the integrating server.
21pub trait ObjectStore: Send + Sync {
22    fn load(&self, object_id: &str) -> ObjectStoreResult<Vec<u8>>;
23    fn save(&self, bytes: &[u8]) -> ObjectStoreResult<String>;
24}
25
26/// One complete editable managed-binary source snapshot.
27pub struct Lib {
28    /// Every useful UTF-8 source file, canonically sorted after open or write.
29    pub files: Vec<File>,
30    repository: PathBuf,
31    name: String,
32    identity: String,
33    object_store: Arc<dyn ObjectStore>,
34}
35
36/// Creates a minimal Rust 2024 binary and returns its complete source.
37pub fn create(
38    rust_bins_root: impl AsRef<Path>,
39    name: &str,
40    object_store: Arc<dyn ObjectStore>,
41) -> Result<Lib> {
42    validate_name(name)?;
43    let source = initial_source(name)?;
44    let snapshot = repository::create(rust_bins_root.as_ref(), name, &source)?;
45    Ok(Lib {
46        files: snapshot.files,
47        repository: snapshot.repository,
48        name: name.to_owned(),
49        identity: snapshot.identity,
50        object_store,
51    })
52}
53
54/// Opens an existing binary, migrating a valid legacy flat repository when needed.
55pub fn open(
56    rust_bins_root: impl AsRef<Path>,
57    name: &str,
58    object_store: Arc<dyn ObjectStore>,
59) -> Result<Lib> {
60    validate_name(name)?;
61    let snapshot = repository::open(rust_bins_root.as_ref(), name)?;
62    Ok(Lib {
63        files: snapshot.files,
64        repository: snapshot.repository,
65        name: name.to_owned(),
66        identity: snapshot.identity,
67        object_store,
68    })
69}
70
71/// Returns the package version and root `Documentation.md`, migrating when needed.
72pub fn docs(rust_bins_root: impl AsRef<Path>, name: &str) -> Result<(String, String)> {
73    validate_name(name)?;
74    repository::docs(rust_bins_root.as_ref(), name)
75}
76
77/// Attempts to execute bytes from any supplied object ID.
78pub fn run(
79    object_store: &dyn ObjectStore,
80    binary_object_id: &str,
81    request: &RunRequest,
82) -> Result<RunResult> {
83    execution::run(object_store, binary_object_id, request)
84}
85
86impl Lib {
87    /// Atomically commits `files` as the complete source if this snapshot is current.
88    pub fn write(&mut self) -> Result<()> {
89        let source = validate_source(&self.files, &self.name)?;
90        let snapshot = repository::replace(&self.repository, &self.identity, &source)?;
91        self.files = snapshot.files;
92        self.identity = snapshot.identity;
93        Ok(())
94    }
95
96    /// Validates and statically builds exactly the current in-memory source.
97    pub fn check(&self) -> Result<()> {
98        let source = validate_source(&self.files, &self.name)?;
99        sandbox::check(&source, &self.name)
100    }
101
102    /// Validates once, saves the exact executable, and returns its object ID.
103    pub fn publish(&self) -> Result<String> {
104        let source = validate_source(&self.files, &self.name)?;
105        let executable = sandbox::build(&source, &self.name)?;
106        self.object_store
107            .save(&executable)
108            .map_err(|error| Error::object_store("save executable", None, error))
109    }
110}
111
112impl fmt::Debug for Lib {
113    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
114        formatter
115            .debug_struct("Lib")
116            .field("files", &self.files)
117            .field("repository", &self.repository)
118            .field("name", &self.name)
119            .field("identity", &"[PRIVATE]")
120            .field("object_store", &"[PRIVATE]")
121            .finish()
122    }
123}
124
125fn initial_source(name: &str) -> Result<ValidSource> {
126    validate_source(
127        &[
128            File {
129                path: "Cargo.toml".to_owned(),
130                contents: format!(
131                    "[package]\nname = \"{name}\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\n"
132                ),
133            },
134            File {
135                path: "Documentation.md".to_owned(),
136                contents: String::new(),
137            },
138            File {
139                path: "src/main.rs".to_owned(),
140                contents: "fn main() {}\n".to_owned(),
141            },
142        ],
143        name,
144    )
145}