Skip to main content

runmat_package/graph/
model.rs

1use 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}