1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
//! Immutable catalog snapshots and indices.
use std::collections::BTreeMap;
use std::sync::Arc;
use thiserror::Error;
use crate::definition::{SetTemplate, SetTemplateKind, SourceManifest};
use crate::overlay::{Advisory, AppliedPatch};
/// Selected source-policy profile.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CatalogProfile {
/// Unmodified upstream publication data, no correction overlay applied.
Official,
/// Official data with the built-in correction overlay's patches applied.
Corrected,
/// Official or corrected data with a caller-supplied overlay applied.
Custom,
}
/// Immutable, cheaply cloneable template snapshot.
#[derive(Debug, Clone)]
pub struct Catalog(Arc<CatalogInner>);
#[derive(Debug, Clone)]
struct CatalogInner {
manifest: SourceManifest,
profile: CatalogProfile,
templates: Vec<SetTemplate>,
by_name: BTreeMap<String, usize>,
applied_patches: Vec<AppliedPatch>,
advisories: Vec<Advisory>,
}
impl Catalog {
/// Build a snapshot for [`CatalogProfile::Official`] or
/// [`CatalogProfile::Custom`]. Fails on duplicate/empty template names or
/// a manifest set-count mismatch; use [`crate::overlay`] to build a
/// [`CatalogProfile::Corrected`] snapshot instead, since this rejects that
/// profile directly.
pub fn try_new(
manifest: SourceManifest,
profile: CatalogProfile,
templates: Vec<SetTemplate>,
) -> Result<Self, CatalogError> {
if profile == CatalogProfile::Corrected {
return Err(CatalogError::CorrectedProfileRequiresPatches);
}
Self::try_new_with_profile(manifest, profile, templates)
}
/// Build a snapshot for any profile, including [`CatalogProfile::Corrected`].
/// Reserved for callers within this crate that have already produced an
/// applied-patch ledger.
pub(crate) fn try_new_with_profile(
manifest: SourceManifest,
profile: CatalogProfile,
templates: Vec<SetTemplate>,
) -> Result<Self, CatalogError> {
let mut by_name = BTreeMap::new();
let mut property_sets = 0;
let mut quantity_sets = 0;
for (index, template) in templates.iter().enumerate() {
if template.name.trim().is_empty() {
return Err(CatalogError::EmptyTemplateName);
}
if by_name.insert(template.name.clone(), index).is_some() {
return Err(CatalogError::DuplicateTemplate(template.name.clone()));
}
match template.kind {
SetTemplateKind::Property { .. } => property_sets += 1,
SetTemplateKind::Quantity { .. } => quantity_sets += 1,
}
}
if manifest.property_set_count != property_sets
|| manifest.quantity_set_count != quantity_sets
{
return Err(CatalogError::ManifestCountMismatch {
expected_property_sets: manifest.property_set_count,
actual_property_sets: property_sets,
expected_quantity_sets: manifest.quantity_set_count,
actual_quantity_sets: quantity_sets,
});
}
Ok(Self(Arc::new(CatalogInner {
manifest,
profile,
templates,
by_name,
applied_patches: Vec::new(),
advisories: Vec::new(),
})))
}
/// Look up a set template by its exact `Name`.
pub fn get(&self, name: &str) -> Option<&SetTemplate> {
self.0
.by_name
.get(name)
.map(|index| &self.0.templates[*index])
}
/// Iterate every set template in this snapshot, in load order.
pub fn iter(&self) -> impl ExactSizeIterator<Item = &SetTemplate> {
self.0.templates.iter()
}
/// Total number of set templates in this snapshot.
pub fn len(&self) -> usize {
self.0.templates.len()
}
/// True when the snapshot holds no set templates.
pub fn is_empty(&self) -> bool {
self.0.templates.is_empty()
}
/// Source-policy profile this snapshot was built with.
pub fn profile(&self) -> CatalogProfile {
self.0.profile
}
/// Source manifest this snapshot was built from.
pub fn manifest(&self) -> &SourceManifest {
&self.0.manifest
}
/// Overlay patches that were applied to reach this snapshot, empty
/// unless `profile()` is [`CatalogProfile::Corrected`] or [`CatalogProfile::Custom`].
pub fn applied_patches(&self) -> &[AppliedPatch] {
&self.0.applied_patches
}
/// Advisories targeting the named template, empty if none apply.
pub fn advisories_for(&self, template: &str) -> Vec<&Advisory> {
self.0
.advisories
.iter()
.filter(|advisory| advisory.target_template == template)
.collect()
}
/// Every advisory recorded against this snapshot, regardless of target.
pub(crate) fn advisories(&self) -> &[Advisory] {
&self.0.advisories
}
/// Replace this snapshot's profile, applied-patch ledger, and advisories
/// in place, cloning the shared inner state first if other handles exist.
pub(crate) fn with_overlay_state(
mut self,
profile: CatalogProfile,
applied_patches: Vec<AppliedPatch>,
advisories: Vec<Advisory>,
) -> Self {
let inner = Arc::make_mut(&mut self.0);
inner.profile = profile;
inner.applied_patches = applied_patches;
inner.advisories = advisories;
self
}
/// Unwrap the snapshot into its owned template list, cloning only if
/// another handle to the shared inner state is still alive.
pub(crate) fn into_templates(self) -> Vec<SetTemplate> {
match Arc::try_unwrap(self.0) {
Ok(inner) => inner.templates,
Err(inner) => inner.templates.clone(),
}
}
}
/// Catalog construction failure.
#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[non_exhaustive]
pub enum CatalogError {
/// [`Catalog::try_new`] was called with [`CatalogProfile::Corrected`];
/// that profile can only be built by applying an overlay's patch ledger.
#[error("the corrected profile requires an applied patch ledger")]
CorrectedProfileRequiresPatches,
/// A template's `name` was empty or whitespace-only.
#[error("template name is empty")]
EmptyTemplateName,
/// Two templates in the input shared the same `name`.
#[error("duplicate template `{0}`")]
DuplicateTemplate(String),
/// The manifest's declared property-set/quantity-set counts did not
/// match the counts actually present in the template list.
#[error(
"source manifest counts differ: property sets {actual_property_sets}/{expected_property_sets}, quantity sets {actual_quantity_sets}/{expected_quantity_sets}"
)]
ManifestCountMismatch {
/// Property-set count declared by the manifest.
expected_property_sets: usize,
/// Property-set count actually found in the template list.
actual_property_sets: usize,
/// Quantity-set count declared by the manifest.
expected_quantity_sets: usize,
/// Quantity-set count actually found in the template list.
actual_quantity_sets: usize,
},
}