greentic_ext_runtime/
capability.rs1use std::collections::HashMap;
2
3use greentic_extension_sdk_contract::{CapabilityId, CapabilityRef, ExtensionKind};
4use semver::{Version, VersionReq};
5
6#[derive(Debug, Clone)]
7pub struct OfferedBinding {
8 pub extension_id: String,
9 pub cap_id: CapabilityId,
10 pub version: Version,
11 pub kind: ExtensionKind,
12 pub export_path: String,
13}
14
15#[derive(Debug, Clone, Default)]
16pub struct ResolutionPlan {
17 pub consumer: String,
18 pub resolved: HashMap<CapabilityId, OfferedBinding>,
19 pub unresolved: Vec<CapabilityRef>,
20}
21
22#[derive(Debug, Default)]
23pub struct CapabilityRegistry {
24 offerings: HashMap<CapabilityId, Vec<OfferedBinding>>,
25}
26
27impl CapabilityRegistry {
28 #[must_use]
29 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub fn add_offering(&mut self, offering: OfferedBinding) {
34 self.offerings
35 .entry(offering.cap_id.clone())
36 .or_default()
37 .push(offering);
38 }
39
40 #[must_use]
41 pub fn resolve(&self, consumer: &str, required: &[CapabilityRef]) -> ResolutionPlan {
42 let mut resolved = HashMap::new();
43 let mut unresolved = Vec::new();
44 for req in required {
45 let vr = VersionReq::parse(&req.version).unwrap_or(VersionReq::STAR);
46 let best = self
47 .offerings
48 .get(&req.id)
49 .and_then(|offers| {
50 offers
51 .iter()
52 .filter(|o| vr.matches(&o.version))
53 .max_by(|a, b| a.version.cmp(&b.version))
54 })
55 .cloned();
56 match best {
57 Some(o) => {
58 resolved.insert(req.id.clone(), o);
59 }
60 None => unresolved.push(req.clone()),
61 }
62 }
63 ResolutionPlan {
64 consumer: consumer.to_string(),
65 resolved,
66 unresolved,
67 }
68 }
69
70 pub fn offerings(&self) -> impl Iterator<Item = &OfferedBinding> {
71 self.offerings.values().flat_map(|v| v.iter())
72 }
73
74 #[must_use]
77 pub fn detect_cycle(&self, extensions: &[(String, Vec<CapabilityRef>)]) -> Vec<String> {
78 let ext_map: HashMap<&str, &Vec<CapabilityRef>> = extensions
79 .iter()
80 .map(|(id, reqs)| (id.as_str(), reqs))
81 .collect();
82
83 let mut in_cycle = Vec::new();
84 for (id, _) in extensions {
85 let mut visited = std::collections::HashSet::new();
86 if self.dfs_has_cycle(id, &ext_map, &mut visited) {
87 in_cycle.push(id.clone());
88 }
89 }
90 in_cycle
91 }
92
93 fn dfs_has_cycle(
94 &self,
95 ext_id: &str,
96 ext_map: &HashMap<&str, &Vec<CapabilityRef>>,
97 visited: &mut std::collections::HashSet<String>,
98 ) -> bool {
99 if !visited.insert(ext_id.to_string()) {
100 return true;
101 }
102 let Some(reqs) = ext_map.get(ext_id) else {
103 visited.remove(ext_id);
104 return false;
105 };
106 for req in *reqs {
107 let vr = VersionReq::parse(&req.version).unwrap_or(VersionReq::STAR);
108 let Some(offers) = self.offerings.get(&req.id) else {
109 continue;
110 };
111 for o in offers.iter().filter(|o| vr.matches(&o.version)) {
112 if self.dfs_has_cycle(&o.extension_id, ext_map, visited) {
113 return true;
114 }
115 }
116 }
117 visited.remove(ext_id);
118 false
119 }
120}