1use crate::bindings::{BindingValue, ResolvedBindings};
4use crate::contracts::{BindingKey, BindingKind, BindingReference, ContractVersion, ExtensionId};
5use crate::error::FrameworkError;
6use crate::ontology::{OntologyContractDiagnostic, verify_ontology_contracts};
7use crate::registry::{ActiveExtensions, ExtensionRegistry};
8use knowledge_base_models::{EntityTypeId, PropertyId};
9use knowledge_base_snapshot::{Error as SnapshotError, RepositorySnapshot};
10use serde::{Deserialize, Serialize};
11use std::collections::{BTreeMap, BTreeSet};
12use std::fmt;
13use std::fs;
14use std::io;
15use std::path::{Path, PathBuf};
16
17pub const MANIFEST_FILE_NAME: &str = "extensions.yaml";
18pub const MANIFEST_VERSION: u32 = 1;
19
20#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
22#[serde(deny_unknown_fields)]
23pub struct ExtensionManifest {
24 pub version: u32,
25 #[serde(default)]
26 pub extensions: BTreeMap<ExtensionId, ManifestExtension>,
27}
28
29#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
31#[serde(deny_unknown_fields)]
32pub struct ManifestExtension {
33 pub contract: ContractVersion,
34 #[serde(default)]
35 pub entity_types: BTreeMap<BindingKey, EntityTypeId>,
36 #[serde(default)]
37 pub properties: BTreeMap<BindingKey, PropertyId>,
38}
39
40#[derive(Clone, Debug, Eq, PartialEq)]
42pub enum ManifestDiagnostic {
43 UnsupportedVersion {
44 version: u32,
45 },
46 UnavailableExtension {
47 extension: ExtensionId,
48 },
49 UnsupportedContract {
50 extension: ExtensionId,
51 declared: ContractVersion,
52 available: ContractVersion,
53 },
54 MissingDependency {
55 extension: ExtensionId,
56 dependency: ExtensionId,
57 },
58 DuplicateBinding {
59 binding: BindingReference,
60 },
61 UndeclaredBinding {
62 binding: BindingReference,
63 },
64 BindingKindMismatch {
65 binding: BindingReference,
66 expected: BindingKind,
67 actual: BindingKind,
68 },
69 MissingBinding {
70 binding: BindingReference,
71 },
72 MissingEntityType {
73 binding: BindingReference,
74 id: EntityTypeId,
75 },
76 MissingProperty {
77 binding: BindingReference,
78 id: PropertyId,
79 },
80}
81
82impl fmt::Display for ManifestDiagnostic {
83 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
84 match self {
85 Self::UnsupportedVersion { version } => write!(formatter, "unsupported extension manifest version {version}; expected {MANIFEST_VERSION}"),
86 Self::UnavailableExtension { extension } => write!(formatter, "extension {extension} is not compiled into this distribution"),
87 Self::UnsupportedContract { extension, declared, available } => write!(
88 formatter,
89 "extension {extension} declares contract {declared}, but compiled implementation supports {available}"
90 ),
91 Self::MissingDependency { extension, dependency } => write!(formatter, "extension {extension} requires declared dependency {dependency}"),
92 Self::DuplicateBinding { binding } => write!(formatter, "binding {binding} is declared in more than one typed segment"),
93 Self::UndeclaredBinding { binding } => write!(formatter, "extension does not declare binding {binding}"),
94 Self::BindingKindMismatch { binding, expected, actual } => write!(formatter, "binding {binding} is declared as {actual:?}, but extension requires {expected:?}"),
95 Self::MissingBinding { binding } => write!(formatter, "manifest is missing required binding {binding}"),
96 Self::MissingEntityType { binding, id } => write!(formatter, "binding {binding} resolves missing entity type {id}"),
97 Self::MissingProperty { binding, id } => write!(formatter, "binding {binding} resolves missing property {id}"),
98 }
99 }
100}
101
102#[derive(Debug)]
104pub enum ManifestError {
105 Read { path: PathBuf, source: io::Error },
106 Parse { path: PathBuf, source: serde_yaml::Error },
107 Diagnostics { path: PathBuf, diagnostics: Vec<ManifestDiagnostic> },
108 OntologyContracts { path: PathBuf, diagnostics: Vec<OntologyContractDiagnostic> },
109 Framework(FrameworkError),
110 Snapshot(SnapshotError),
111}
112
113impl fmt::Display for ManifestError {
114 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
115 match self {
116 Self::Read { path, source } => write!(formatter, "cannot read extension manifest {}: {source}", path.display()),
117 Self::Parse { path, source } => write!(formatter, "cannot parse extension manifest {}: {source}", path.display()),
118 Self::Diagnostics { path, diagnostics } => write!(
119 formatter,
120 "invalid extension manifest {}: {}",
121 path.display(),
122 diagnostics.iter().map(ToString::to_string).collect::<Vec<_>>().join("; ")
123 ),
124 Self::OntologyContracts { path, diagnostics } => write!(
125 formatter,
126 "invalid extension ontology contracts for {}: {}",
127 path.display(),
128 diagnostics.iter().map(ToString::to_string).collect::<Vec<_>>().join("; ")
129 ),
130 Self::Framework(source) => source.fmt(formatter),
131 Self::Snapshot(source) => source.fmt(formatter),
132 }
133 }
134}
135
136impl std::error::Error for ManifestError {
137 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
138 match self {
139 Self::Read { source, .. } => Some(source),
140 Self::Parse { source, .. } => Some(source),
141 Self::Framework(source) => Some(source),
142 Self::Snapshot(source) => Some(source),
143 Self::Diagnostics { .. } | Self::OntologyContracts { .. } => None,
144 }
145 }
146}
147
148pub struct ManifestActivation {
150 active: ActiveExtensions,
151 bindings: ResolvedBindings,
152}
153
154impl fmt::Debug for ManifestActivation {
155 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
156 formatter
157 .debug_struct("ManifestActivation")
158 .field("active_extension_count", &self.active.extensions().len())
159 .field("binding_count", &self.bindings.values.len())
160 .finish()
161 }
162}
163
164impl ManifestActivation {
165 pub fn active(&self) -> &ActiveExtensions {
166 &self.active
167 }
168
169 pub fn bindings(&self) -> &ResolvedBindings {
170 &self.bindings
171 }
172}
173
174impl ExtensionManifest {
175 pub fn path(root: impl AsRef<Path>) -> PathBuf {
176 root.as_ref().join(MANIFEST_FILE_NAME)
177 }
178
179 pub fn load(root: impl AsRef<Path>) -> Result<Self, ManifestError> {
181 let path = Self::path(root);
182 let source = fs::read_to_string(&path).map_err(|source| ManifestError::Read { path: path.clone(), source })?;
183 serde_yaml::from_str(&source).map_err(|source| ManifestError::Parse { path, source })
184 }
185
186 pub fn load_and_activate(root: impl AsRef<Path>, registry: &ExtensionRegistry) -> Result<ManifestActivation, ManifestError> {
188 let root = root.as_ref();
189 Self::load(root)?.activate(root, registry)
190 }
191
192 pub fn activate(&self, root: impl AsRef<Path>, registry: &ExtensionRegistry) -> Result<ManifestActivation, ManifestError> {
194 let root = root.as_ref();
195 let path = Self::path(root);
196 let mut diagnostics = Vec::new();
197 if self.version != MANIFEST_VERSION {
198 diagnostics.push(ManifestDiagnostic::UnsupportedVersion { version: self.version });
199 }
200
201 let mut available = BTreeSet::new();
202 for (id, extension) in &self.extensions {
203 match registry.metadata(id) {
204 None => diagnostics.push(ManifestDiagnostic::UnavailableExtension { extension: id.clone() }),
205 Some(metadata) if metadata.contract != extension.contract => diagnostics.push(ManifestDiagnostic::UnsupportedContract {
206 extension: id.clone(),
207 declared: extension.contract,
208 available: metadata.contract,
209 }),
210 Some(_) => {
211 available.insert(id.clone());
212 }
213 }
214 }
215
216 for id in &available {
217 let metadata = registry.metadata(id).expect("available extensions have metadata");
218 for dependency in &metadata.dependencies {
219 if !self.extensions.contains_key(&dependency.id) {
220 diagnostics.push(ManifestDiagnostic::MissingDependency {
221 extension: id.clone(),
222 dependency: dependency.id.clone(),
223 });
224 }
225 }
226 }
227
228 let mut values = BTreeMap::new();
229 for (id, extension) in &self.extensions {
230 let Some(metadata) = registry.metadata(id) else {
231 continue;
232 };
233 for (key, value) in &extension.entity_types {
234 let reference = BindingReference::new(id.clone(), key.clone());
235 validate_binding(metadata, &reference, BindingValue::EntityType(value.clone()), &mut values, &mut diagnostics);
236 }
237 for (key, value) in &extension.properties {
238 let reference = BindingReference::new(id.clone(), key.clone());
239 validate_binding(metadata, &reference, BindingValue::Property(value.clone()), &mut values, &mut diagnostics);
240 }
241 let mut declarations = metadata.bindings.iter().collect::<Vec<_>>();
242 declarations.sort_by(|left, right| left.key.cmp(&right.key));
243 for declaration in declarations {
244 let reference = BindingReference::new(id.clone(), declaration.key.clone());
245 if !values.contains_key(&reference) {
246 diagnostics.push(ManifestDiagnostic::MissingBinding { binding: reference });
247 }
248 }
249 }
250
251 if !diagnostics.is_empty() {
252 return Err(ManifestError::Diagnostics { path, diagnostics });
253 }
254
255 let active = registry.resolve_active(self.extensions.keys().cloned()).map_err(ManifestError::Framework)?;
256 let bindings = active.resolve_bindings(values).map_err(ManifestError::Framework)?;
257 let snapshot = RepositorySnapshot::load(root).map_err(ManifestError::Snapshot)?;
258 let mut diagnostics = Vec::new();
259 for (binding, value) in &bindings.values {
260 match value {
261 BindingValue::EntityType(id) if !snapshot.entity_types().contains_key(id) => {
262 diagnostics.push(ManifestDiagnostic::MissingEntityType {
263 binding: binding.clone(),
264 id: id.clone(),
265 });
266 }
267 BindingValue::Property(id) if !snapshot.properties().contains_key(id) => {
268 diagnostics.push(ManifestDiagnostic::MissingProperty {
269 binding: binding.clone(),
270 id: id.clone(),
271 });
272 }
273 _ => {}
274 }
275 }
276 if !diagnostics.is_empty() {
277 return Err(ManifestError::Diagnostics {
278 path: Self::path(root),
279 diagnostics,
280 });
281 }
282 let diagnostics = verify_ontology_contracts(&snapshot, &active, &bindings);
283 if !diagnostics.is_empty() {
284 return Err(ManifestError::OntologyContracts {
285 path: Self::path(root),
286 diagnostics,
287 });
288 }
289 Ok(ManifestActivation { active, bindings })
290 }
291}
292
293fn validate_binding(
294 metadata: &crate::contracts::ExtensionMetadata,
295 reference: &BindingReference,
296 value: BindingValue,
297 values: &mut BTreeMap<BindingReference, BindingValue>,
298 diagnostics: &mut Vec<ManifestDiagnostic>,
299) {
300 let Some(declaration) = metadata.bindings.iter().find(|declaration| declaration.key == *reference.key()) else {
301 diagnostics.push(ManifestDiagnostic::UndeclaredBinding { binding: reference.clone() });
302 return;
303 };
304 if declaration.kind != value.kind() {
305 diagnostics.push(ManifestDiagnostic::BindingKindMismatch {
306 binding: reference.clone(),
307 expected: declaration.kind,
308 actual: value.kind(),
309 });
310 return;
311 }
312 if values.insert(reference.clone(), value).is_some() {
313 diagnostics.push(ManifestDiagnostic::DuplicateBinding { binding: reference.clone() });
314 }
315}