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 publication;
7mod repository;
8mod sandbox;
9
10use std::fmt;
11use std::path::{Path, PathBuf};
12use std::time::Duration;
13
14pub use model::{
15    Error, File, ObjectStoreError, ObjectStoreResult, Result, RunRequest, RunResult, RustBinInput,
16    RustBinOutput,
17};
18use model::{ValidSource, validate_name, validate_source};
19pub use protocol::{read_input, write_output};
20
21/// Policy-blind immutable payload storage supplied by the integrating server.
22///
23/// Published executables do not use this store. It is used only to resolve
24/// ordered call inputs and save ordered call outputs.
25pub trait ObjectStore: Send + Sync {
26    fn load(&self, object_id: &str) -> ObjectStoreResult<Vec<u8>>;
27    fn save(&self, bytes: &[u8]) -> ObjectStoreResult<String>;
28}
29
30/// Default maximum running time for one binary call.
31pub const DEFAULT_RUN_TIMEOUT: Duration = Duration::from_secs(2 * 60);
32
33/// One complete editable managed-binary source snapshot.
34pub struct Lib {
35    /// Every useful UTF-8 source file, canonically sorted after open or write.
36    pub files: Vec<File>,
37    repository: PathBuf,
38    name: String,
39    identity: String,
40    publications: PathBuf,
41}
42
43/// Creates a minimal Rust 2024 binary and returns its complete source.
44pub fn create(
45    rust_bins_root: impl AsRef<Path>,
46    publications_root: impl AsRef<Path>,
47    name: &str,
48) -> Result<Lib> {
49    validate_name(name)?;
50    let source = initial_source(name)?;
51    let snapshot = repository::create(rust_bins_root.as_ref(), name, &source)?;
52    Ok(Lib {
53        files: snapshot.files,
54        repository: snapshot.repository,
55        name: name.to_owned(),
56        identity: snapshot.identity,
57        publications: publications_root.as_ref().to_path_buf(),
58    })
59}
60
61/// Opens an existing binary, migrating a valid legacy flat repository when needed.
62pub fn open(
63    rust_bins_root: impl AsRef<Path>,
64    publications_root: impl AsRef<Path>,
65    name: &str,
66) -> Result<Lib> {
67    validate_name(name)?;
68    let snapshot = repository::open(rust_bins_root.as_ref(), name)?;
69    Ok(Lib {
70        files: snapshot.files,
71        repository: snapshot.repository,
72        name: name.to_owned(),
73        identity: snapshot.identity,
74        publications: publications_root.as_ref().to_path_buf(),
75    })
76}
77
78/// Returns the package version and root `Documentation.md`, migrating when needed.
79pub fn docs(rust_bins_root: impl AsRef<Path>, name: &str) -> Result<(String, String)> {
80    validate_name(name)?;
81    repository::docs(rust_bins_root.as_ref(), name)
82}
83
84/// Resolves and executes a locally published binary.
85///
86/// `version_selector` follows the Web-library convention: `v1.2.3` is exact,
87/// while unprefixed and abbreviated selectors are Cargo-compatible
88/// requirements. `None` uses [`DEFAULT_RUN_TIMEOUT`].
89pub fn run(
90    publications_root: impl AsRef<Path>,
91    object_store: &dyn ObjectStore,
92    name: &str,
93    version_selector: &str,
94    request: &RunRequest,
95    timeout: Option<Duration>,
96) -> Result<RunResult> {
97    validate_name(name)?;
98    let publication = publication::resolve(publications_root.as_ref(), name, version_selector)?;
99    execution::run(
100        object_store,
101        &publication.executable,
102        publication.version.to_string(),
103        request,
104        timeout.unwrap_or(DEFAULT_RUN_TIMEOUT),
105    )
106}
107
108impl Lib {
109    /// Atomically commits `files` as the complete source if this snapshot is current.
110    pub fn write(&mut self) -> Result<()> {
111        let source = validate_source(&self.files, &self.name)?;
112        let snapshot = repository::replace(&self.repository, &self.identity, &source)?;
113        self.files = snapshot.files;
114        self.identity = snapshot.identity;
115        Ok(())
116    }
117
118    /// Validates and statically builds exactly the current in-memory source.
119    pub fn check(&self) -> Result<()> {
120        let source = validate_source(&self.files, &self.name)?;
121        sandbox::check(&source, &self.name)
122    }
123
124    /// Validates once and atomically publishes this exact name and version.
125    pub fn publish(&self) -> Result<()> {
126        let source = validate_source(&self.files, &self.name)?;
127        let executable = sandbox::build(&source, &self.name)?;
128        publication::publish(&self.publications, &self.name, &source.version, &executable)
129    }
130}
131
132impl fmt::Debug for Lib {
133    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
134        formatter
135            .debug_struct("Lib")
136            .field("files", &self.files)
137            .field("repository", &self.repository)
138            .field("name", &self.name)
139            .field("identity", &"[PRIVATE]")
140            .field("publications", &self.publications)
141            .finish()
142    }
143}
144
145fn initial_source(name: &str) -> Result<ValidSource> {
146    validate_source(
147        &[
148            File {
149                path: "Cargo.toml".to_owned(),
150                contents: format!(
151                    "[package]\nname = \"{name}\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\n"
152                ),
153            },
154            File {
155                path: "Documentation.md".to_owned(),
156                contents: String::new(),
157            },
158            File {
159                path: "src/main.rs".to_owned(),
160                contents: "fn main() {}\n".to_owned(),
161            },
162        ],
163        name,
164    )
165}