runmat_package/source/
inventory.rs1use crate::{ContentDigest, IdentityError, NormalizedRelativePath};
2use runmat_config::project::ProjectSourceIndex;
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeSet;
5
6pub const SOURCE_INVENTORY_SCHEMA_VERSION: u32 = 1;
7
8#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
9#[serde(deny_unknown_fields)]
10pub struct SourceInventoryEntry {
11 pub source_root: NormalizedRelativePath,
12 pub relative_path: NormalizedRelativePath,
13 pub qualified_name: String,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub package_path: Option<String>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub class_name: Option<String>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub class_qualified_name: Option<String>,
20 pub is_private: bool,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(deny_unknown_fields)]
25pub struct SourceInventory {
26 pub schema_version: u32,
27 pub tree_digest: ContentDigest,
28 pub entries: Vec<SourceInventoryEntry>,
29 pub package_dirs: Vec<NormalizedRelativePath>,
30 pub class_dirs: Vec<NormalizedRelativePath>,
31 pub private_dirs: Vec<NormalizedRelativePath>,
32}
33
34impl SourceInventory {
35 pub fn from_project_index(
36 tree_digest: ContentDigest,
37 index: ProjectSourceIndex,
38 ) -> Result<Self, IdentityError> {
39 let mut entries = index
40 .files
41 .into_iter()
42 .map(|source| {
43 Ok(SourceInventoryEntry {
44 source_root: NormalizedRelativePath::new(source.source_root)?,
45 relative_path: NormalizedRelativePath::new(source.relative_path)?,
46 qualified_name: source.qualified_name,
47 package_path: source.package_path,
48 class_name: source.class_name,
49 class_qualified_name: source.class_qualified_name,
50 is_private: source.is_private,
51 })
52 })
53 .collect::<Result<Vec<_>, IdentityError>>()?;
54 entries.sort();
55 let package_dirs = normalize_paths(index.package_dirs)?;
56 let class_dirs = normalize_paths(index.class_dirs)?;
57 let private_dirs = normalize_paths(index.private_dirs)?;
58 let inventory = Self {
59 schema_version: SOURCE_INVENTORY_SCHEMA_VERSION,
60 tree_digest,
61 entries,
62 package_dirs,
63 class_dirs,
64 private_dirs,
65 };
66 inventory
67 .validate()
68 .map_err(|reason| IdentityError::InvalidRelativePath {
69 value: "source inventory".to_string(),
70 reason,
71 })?;
72 Ok(inventory)
73 }
74
75 pub fn validate(&self) -> Result<(), &'static str> {
76 if self.schema_version != SOURCE_INVENTORY_SCHEMA_VERSION {
77 return Err("unsupported source inventory schema");
78 }
79 if self.entries.windows(2).any(|pair| {
80 pair[0] >= pair[1]
81 || (&pair[0].source_root, &pair[0].relative_path)
82 == (&pair[1].source_root, &pair[1].relative_path)
83 }) {
84 return Err("source inventory entries must be strictly sorted");
85 }
86 if !strict_paths(&self.package_dirs)
87 || !strict_paths(&self.class_dirs)
88 || !strict_paths(&self.private_dirs)
89 {
90 return Err("source inventory directories must be strictly sorted");
91 }
92 if self
93 .entries
94 .iter()
95 .any(|entry| entry.qualified_name.trim().is_empty())
96 {
97 return Err("source inventory qualified names must be non-empty");
98 }
99 Ok(())
100 }
101}
102
103fn normalize_paths(
104 paths: Vec<std::path::PathBuf>,
105) -> Result<Vec<NormalizedRelativePath>, IdentityError> {
106 paths
107 .into_iter()
108 .map(NormalizedRelativePath::new)
109 .collect::<Result<BTreeSet<_>, _>>()
110 .map(|paths| paths.into_iter().collect())
111}
112
113fn strict_paths(paths: &[NormalizedRelativePath]) -> bool {
114 paths.windows(2).all(|pair| pair[0] < pair[1])
115}