runmat_package/graph/
model.rs1use super::{digest::compute_graph_digest, GraphEdge, VisibilityResolution};
2use crate::{
3 CanonicalPackageId, ContentDigest, GraphError, HostCapability, PackageAlias, PackageInstanceId,
4};
5use serde::{Deserialize, Serialize};
6use std::collections::{BTreeMap, BTreeSet};
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(deny_unknown_fields)]
10pub struct GraphPackage {
11 pub instance: PackageInstanceId,
12 pub local_name: String,
13 pub required_capabilities: BTreeSet<HostCapability>,
14 pub singleton: bool,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(deny_unknown_fields)]
19pub struct PackageGraph {
20 pub root: ContentDigest,
21 pub packages: BTreeMap<ContentDigest, GraphPackage>,
22 pub edges: Vec<GraphEdge>,
23 pub graph_digest: ContentDigest,
24}
25
26impl PackageGraph {
27 pub(crate) fn finish(
28 root: ContentDigest,
29 packages: BTreeMap<ContentDigest, GraphPackage>,
30 mut edges: Vec<GraphEdge>,
31 ) -> Result<Self, GraphError> {
32 if !packages.contains_key(&root) {
33 return Err(GraphError::Invalid(
34 "root package instance is missing".to_string(),
35 ));
36 }
37 edges.sort();
38 let graph_digest = compute_graph_digest(&root, &packages, &edges)?;
39 Ok(Self {
40 root,
41 packages,
42 edges,
43 graph_digest,
44 })
45 }
46
47 pub fn dependency(&self, from: &ContentDigest, alias: &PackageAlias) -> Option<&GraphPackage> {
48 self.edges
49 .iter()
50 .find(|edge| &edge.from == from && &edge.alias == alias)
51 .and_then(|edge| self.packages.get(&edge.to))
52 }
53
54 pub fn resolve_visible_candidates(
55 &self,
56 requester: &ContentDigest,
57 candidates: impl IntoIterator<Item = ContentDigest>,
58 ) -> VisibilityResolution {
59 super::visibility::resolve(self, requester, candidates)
60 }
61
62 pub fn instances_of(&self, package: &CanonicalPackageId) -> Vec<&GraphPackage> {
63 self.packages
64 .values()
65 .filter(|candidate| &candidate.instance.package == package)
66 .collect()
67 }
68
69 pub(crate) fn validate_digest(&self) -> Result<(), GraphError> {
70 if !self.packages.contains_key(&self.root) {
71 return Err(GraphError::Invalid(
72 "root package instance is missing".to_string(),
73 ));
74 }
75 for (identity, package) in &self.packages {
76 if identity != &package.instance.identity_digest {
77 return Err(GraphError::Invalid(format!(
78 "package map key {identity} does not match its instance identity {}",
79 package.instance.identity_digest
80 )));
81 }
82 package
83 .instance
84 .source
85 .validate()
86 .map_err(|error| GraphError::Invalid(error.to_string()))?;
87 if package.instance.source.tree_digest() != &package.instance.tree_digest {
88 return Err(GraphError::Invalid(format!(
89 "package {identity} source and instance tree digests differ"
90 )));
91 }
92 let expected = PackageInstanceId::new(
93 package.instance.package.clone(),
94 package.instance.source.clone(),
95 package.instance.version.clone(),
96 package.instance.tree_digest.clone(),
97 );
98 if expected.identity_digest != package.instance.identity_digest {
99 return Err(GraphError::Invalid(format!(
100 "package {identity} identity digest does not match its canonical instance"
101 )));
102 }
103 }
104 for edge in &self.edges {
105 if !self.packages.contains_key(&edge.from) || !self.packages.contains_key(&edge.to) {
106 return Err(GraphError::Invalid(format!(
107 "dependency edge `{}` references an absent package instance",
108 edge.alias
109 )));
110 }
111 }
112 if self.edges.windows(2).any(|pair| pair[0] > pair[1]) {
113 return Err(GraphError::Invalid(
114 "dependency edges are not in canonical order".to_string(),
115 ));
116 }
117 let mut aliases = BTreeSet::new();
118 for edge in &self.edges {
119 if !aliases.insert((edge.from.clone(), edge.alias.clone())) {
120 return Err(GraphError::Invalid(format!(
121 "package {} has duplicate dependency alias `{}`",
122 edge.from, edge.alias
123 )));
124 }
125 }
126 let expected =
127 super::digest::compute_graph_digest(&self.root, &self.packages, &self.edges)?;
128 if expected != self.graph_digest {
129 return Err(GraphError::Invalid(
130 "package graph digest does not match its canonical contents".to_string(),
131 ));
132 }
133 Ok(())
134 }
135}