runmat_execution_artifact/bundle/
closure.rs1use runmat_execution::Digest;
2use runmat_package::FrozenProjectHandoff;
3use serde::{Deserialize, Serialize};
4
5use crate::{ArtifactError, ArtifactResult};
6
7#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
8#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
9pub enum BundleCodeClosure {
10 SourceProject { handoff: FrozenProjectHandoff },
11 Compiled { package: CompiledPackageClosure },
12}
13
14#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
15#[serde(deny_unknown_fields)]
16pub struct CompiledPackageClosure {
17 pub schema_version: u16,
18 pub graph_digest: Digest,
19 pub source_digest: Digest,
20 pub package_instances: Vec<String>,
21}
22
23impl CompiledPackageClosure {
24 pub const SCHEMA_VERSION: u16 = 1;
25
26 pub fn validate(&self) -> ArtifactResult<()> {
27 if self.schema_version != Self::SCHEMA_VERSION
28 || self.package_instances.is_empty()
29 || self
30 .package_instances
31 .windows(2)
32 .any(|pair| pair[0] >= pair[1])
33 || self
34 .package_instances
35 .iter()
36 .any(|identity| !valid_identity(identity))
37 {
38 return Err(ArtifactError::Invalid(
39 "compiled package closure is not canonical".into(),
40 ));
41 }
42 Ok(())
43 }
44}
45
46fn valid_identity(value: &str) -> bool {
47 !value.is_empty()
48 && value.len() <= 512
49 && value.is_ascii()
50 && !value.chars().any(char::is_control)
51}