Skip to main content

runmat_package/source/
registry_release.rs

1use crate::{ContentDigest, DependencyGroup, RegistryOrigin, RegistrySourceId};
2use semver::VersionReq;
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase", deny_unknown_fields)]
8pub struct RegistryPackageReference {
9    pub registry: RegistryOrigin,
10    pub namespace: String,
11    pub name: String,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase", deny_unknown_fields)]
16pub struct RegistryReleaseDependency {
17    pub alias: String,
18    pub package: RegistryPackageReference,
19    pub requirement: String,
20    pub group: DependencyGroup,
21    pub target: Option<String>,
22    pub optional: bool,
23    pub default_features: bool,
24    pub features: Vec<String>,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(rename_all = "camelCase", deny_unknown_fields)]
29pub struct RegistryReleaseMetadata {
30    pub singleton: bool,
31    pub runmat_requirement: Option<String>,
32    pub dependencies: Vec<RegistryReleaseDependency>,
33    pub features: BTreeMap<String, Vec<String>>,
34    pub required_capabilities: Vec<String>,
35    pub optional_capabilities: Vec<String>,
36    pub readme_digest: Option<String>,
37    pub license: Option<String>,
38    #[serde(default)]
39    pub encryption: Option<super::EncryptedArtifactMetadata>,
40    #[serde(default)]
41    pub supply_chain: Option<super::RegistryReleaseSupplyChain>,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase", deny_unknown_fields)]
46pub struct RegistryCandidateRecord {
47    pub source: RegistrySourceId,
48    pub metadata: RegistryReleaseMetadata,
49    pub yanked: bool,
50}
51
52impl RegistryReleaseMetadata {
53    pub fn compute_digest(&self, source: &RegistrySourceId) -> Result<ContentDigest, String> {
54        self.validate_semantics()?;
55        #[derive(Serialize)]
56        #[serde(rename_all = "camelCase")]
57        struct CanonicalPackage<'a> {
58            registry: &'a str,
59            namespace: &'a str,
60            name: &'a str,
61        }
62        #[derive(Serialize)]
63        #[serde(rename_all = "camelCase")]
64        struct CanonicalMetadata<'a> {
65            singleton: bool,
66            runmat_requirement: &'a Option<String>,
67            dependencies: &'a [RegistryReleaseDependency],
68            features: &'a BTreeMap<String, Vec<String>>,
69            required_capabilities: &'a [String],
70            optional_capabilities: &'a [String],
71            readme_digest: &'a Option<String>,
72            license: &'a Option<String>,
73        }
74        #[derive(Serialize)]
75        #[serde(rename_all = "camelCase")]
76        struct Canonical<'a> {
77            format: &'static str,
78            package: CanonicalPackage<'a>,
79            version: String,
80            artifact_digest: String,
81            tree_digest: String,
82            metadata: CanonicalMetadata<'a>,
83            #[serde(skip_serializing_if = "Option::is_none")]
84            encryption: Option<&'a super::EncryptedArtifactMetadata>,
85            #[serde(skip_serializing_if = "Option::is_none")]
86            supply_chain: Option<&'a super::RegistryReleaseSupplyChain>,
87        }
88        if let Some(encryption) = &self.encryption {
89            encryption.validate(source)?;
90        }
91        let bytes = serde_json::to_vec(&Canonical {
92            format: if self.encryption.is_some() {
93                "runmat-registry-release-v3"
94            } else if self.supply_chain.is_some() {
95                "runmat-registry-release-v2"
96            } else {
97                "runmat-registry-release-v1"
98            },
99            package: CanonicalPackage {
100                registry: source.registry_origin.as_str(),
101                namespace: source.package.organization(),
102                name: source.package.name(),
103            },
104            version: source.version.to_string(),
105            artifact_digest: source.artifact_digest.to_string(),
106            tree_digest: source.tree_digest.to_string(),
107            metadata: CanonicalMetadata {
108                singleton: self.singleton,
109                runmat_requirement: &self.runmat_requirement,
110                dependencies: &self.dependencies,
111                features: &self.features,
112                required_capabilities: &self.required_capabilities,
113                optional_capabilities: &self.optional_capabilities,
114                readme_digest: &self.readme_digest,
115                license: &self.license,
116            },
117            encryption: self.encryption.as_ref(),
118            supply_chain: self.supply_chain.as_ref(),
119        })
120        .map_err(|error| error.to_string())?;
121        Ok(ContentDigest::sha256(bytes))
122    }
123
124    pub fn validate_source(&self, source: &RegistrySourceId) -> Result<(), String> {
125        source.validate().map_err(|error| error.to_string())?;
126        if self.compute_digest(source)? != source.release_digest {
127            return Err(
128                "registry release metadata digest differs from the exact source".to_string(),
129            );
130        }
131        Ok(())
132    }
133
134    pub fn verify_supply_chain(
135        &self,
136        package_id: &str,
137        source: &RegistrySourceId,
138    ) -> Result<(), String> {
139        self.validate_source(source)?;
140        if let Some(supply_chain) = &self.supply_chain {
141            supply_chain.verify(package_id)?;
142        }
143        Ok(())
144    }
145
146    fn validate_semantics(&self) -> Result<(), String> {
147        if self.dependencies.len() > 512
148            || self.features.len() > 512
149            || self.required_capabilities.len() > 128
150            || self.optional_capabilities.len() > 128
151        {
152            return Err("registry release metadata exceeds semantic limits".to_string());
153        }
154        if self
155            .runmat_requirement
156            .as_deref()
157            .map(VersionReq::parse)
158            .transpose()
159            .is_err()
160            || self.dependencies.iter().any(|dependency| {
161                dependency.alias.is_empty()
162                    || dependency.alias.len() > 64
163                    || VersionReq::parse(&dependency.requirement).is_err()
164                    || dependency
165                        .target
166                        .as_ref()
167                        .is_some_and(|target| target.is_empty() || target.len() > 256)
168                    || dependency.features.len() > 128
169            })
170            || self.features.iter().any(|(feature, requests)| {
171                feature.is_empty()
172                    || feature.len() > 64
173                    || requests.len() > 512
174                    || requests
175                        .iter()
176                        .any(|request| request.is_empty() || request.len() > 129)
177            })
178            || self
179                .required_capabilities
180                .iter()
181                .chain(&self.optional_capabilities)
182                .any(|capability| capability.is_empty() || capability.len() > 64)
183        {
184            return Err("registry release metadata contains invalid semantic fields".to_string());
185        }
186        Ok(())
187    }
188}